#!BPY

"""
Name: 'Frappe'
Blender: 244
Group: 'Render'
Tooltip: 'Render with NVIDIA Gelato(TM)'
"""

__author__ = 'Mario Ambrogetti, Jason Maltzen'
__version__ = '0.17'
__url__ = ['']
__bpydoc__ = """\
Blender to NVIDIA Gelato(TM)
"""
__currentSaveVersion__ = '0.151'

# NVIDIA Gelato(TM) Exporter
#
# By: Mario Ambrogetti, Jason Maltzen
# Date:        Mon, 9 Jul 2007 20:49:09 -0500
#
# ***** BEGIN GPL LICENSE BLOCK *****
#
# Script copyright (C) Mario Ambrogetti, Jason Maltzen
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
#
# ***** END GPL LICENCE BLOCK *****

import sys
print 'Frappe version %s loading!' % __version__

import Blender
import sys, os, re, datetime, fnmatch, struct, copy, string
import tempfile
import xml.dom.minidom
import math
import traceback

try:
	import xml.dom.ext
	USE_XML_DOM_EXT = True
except:
	USE_XML_DOM_EXT = False

try:
	import getpass
	USE_GETPASS = True
except:
	USE_GETPASS = False

mesh_errors = {}


####################################################
# Gelato error exception class
####################################################
class GelatoError(Exception):
	def __init__(self, message):
		self.message = message

	def __str__(self):
		return self.message


####################################################
# Enumeration types
####################################################
class enum_type(object):
	class enum_value(object):
		def __init__(self, name, val):
			self.name = name
			self.val = val
		def __str__(self):
			return self.name
		def __int__(self):
			return self.val

	def __init__(self, *args):
		self.names = list(args)
		for idx, name in enumerate(self.names):
			val = self.enum_value(name, idx)
			setattr(self, name, val)

	def __len__(self):
		return len(self.names)

	def __iter__(self):
		return enumerate(self.names)

	def __contains__(self, item):
		return hasattr(self, item)

	def __getitem__(self, key):
		return getattr(self, key)

	def __str__(self):
		return str([(self.names[idx], idx) for idx in xrange(len(self.names))])

	def has_key(self, key):
		return self.__contains__(key)


##############
# Global types
##############
shader_types = enum_type('surface', 'displacement', 'volume', 'light', 'generic')
literals = enum_type('float', 'string', 'color', 'point', 'vector', 'normal', 'matrix')



####################################################
# Shader groups (layers)
####################################################
class shadergroup(object):
	available_types    = [shader_types.surface, shader_types.displacement]
	def __init__(self, name, shaderType):
		self.name = name
		self.shaders = []
		self.connections = {} # target shader name -> {target parameter -> (source shader name, source parameter))
		self.type = shaderType


####################################################
# Shader object data
####################################################
class shader(object):
	class parameter(object):
		__slots__ = ['type', 'help', 'default', 'change', 'valueCount', 'value']

		def __init__(self, type, value, valueCount, help):
			self.type    = type
			self.help    = help
			self.default = value
			self.change  = False
			self.value   = value

			self.valueCount = 1

		def __deepcopy__(self, memo = {}):
			new_parameter = shader.parameter.__new__(shader.parameter)
			memo[id(self)] = new_parameter

			for attr_name in self.__slots__:
				value = getattr(self, attr_name)

				setattr(new_parameter, attr_name, value)

			return new_parameter

	__slots__ = ['file', 'nameid', 'verbose', 'parameters', 'type',\
		'name', 'enable_sss', 'sss_param', 'layer',\
		'outparams', 'globals']

	def __init__(self, filename = None, nameid = '', verbose = 1):
		global WIN

		self.file       = filename
		self.nameid     = nameid
		self.verbose    = verbose
		self.parameters = {}
		self.outparams  = {}
		self.globals	= {}
		self.type       = None
		self.name       = None
		self.layer	= None
		self.enable_sss = 0
		self.sss_param  = 'diffusefile'

		if (filename and (not self.parse_file())):
			raise GelatoError, 'Invalid shader'

	def __deepcopy__(self, memo = {}):
		new_shader = shader.__new__(shader)
		memo[id(self)] = new_shader

		for attr_name in self.__slots__:
			if (attr_name == 'parameters'):
				value = {}
				for key, data in self.parameters.iteritems():
					value[key] = copy.deepcopy(data)
			else:
				value = getattr(self, attr_name)

			setattr(new_shader, attr_name, value)

		return new_shader

	def __len__(self):
		return len(self.parameters)

	def __iter__(self):
		return enumerate(self.parameters.keys())

	def __getitem__(self, key):
		return self.parameters[key].value

	def __setitem__(self, key, value):
		par = self.parameters[key]
		par.value = str(value)
		par.change = True
		Blender.Draw.Redraw(1)

	def __str__(self):
		raise 'shader.__str__ deprecated'
		if (self.type is None or (not self.name)):
			if (self.verbose > 1):
				print 'Error: null shader'
			return ''

		slist = []
		for name, par in self.parameters.iteritems():

			# skip if no change

			if (not par.change):
				continue

			ty = par.type

			# float

			if (ty is literals.float):
				try:
					slist.append('Parameter ("float %s", %s)\n' %
						(name, float(par.value)))
				except ValueError:
					if (self.verbose > 1):
						print 'Error: parameter not valid "%s"' % par.value
					continue

			# string

			elif (ty is literals.string):
				slist.append('Parameter ("string %s", "%s")\n' %
					(name, par.value.strip()))

			# color, point vector, normal

			elif ((ty is literals.color) or
				(ty is literals.point) or
				(ty is literals.vector) or
				(ty is literals.normal)):
					val = par.value.strip()
					lpar = val.split(' ')
					l = len(lpar)
					if (l == 1):
						slist.append('Parameter ("%s %s", %s)\n' %
							(literals[ty], name, val))
					elif (l == 3):
						slist.append('Parameter ("%s %s", (%s))\n' %
							(literals[ty], name, ', '.join(lpar)))
					else:
						if (self.verbose > 1):
							print 'Error: parameter not valid "%s"' % par.value
						continue

			# TODO matrix

			else:
				if (self.verbose > 1):
					print 'Error: unknown parameter "%s"' % name

		ty = self.type

		# Shader: surface, displacement, volume, generic

		if ((ty is shader_types.surface) or
			(ty is shader_types.displacement) or
			(ty is shader_types.volume) or
			(ty is shader_types.generic)):
				if self.layer is None:
					slist.append('Shader ("%s", "%s")\n' % (str(ty), self.name))
				else:
					slist.append('Shader ("%s", "%s", "%s")\n' % (str(ty), self.name, self.layer))

		# Light

		elif (ty is shader_types.light):
			if self.layer is None:
				slist.append('Light ("%s", "%s")\n' % (self.nameid, self.name))
			else:
				slist.append('Light ("%s", "%s", "%s")\n' % (self.nameid, self.name, self.layer))

		else:
			if (self.verbose > 1):
				print 'Error: unknown type shader "%s"' % str(ty)
			return ''

		return ''.join(slist)


	def default(self):
		for par in self.parameters.itervalues():
			par.value = par.default
			par.change = False


	def parse_file(self):
		self.type = None
		# open file

		filePath = search_file(self.file, SHADERPATH)
		if filePath:
			try:
				fd = open(filePath, 'r')
			except:
				if (self.verbose > 0):
					print 'Error: opening file "%s"' % filePath
				return False
		else:
			print "Error: unable to find shader '%s' in shader path '%s'" % (self.file, SHADERPATH)

		# read file until we hit 'initcode'
		for line in fd:
			line = line.strip()
			if len(line) == 0:
				continue
			if line[0] == '#':
				# skip comment
				continue

			elements = line.split()
			if len(elements) < 2:
				continue
			paramType, name = elements[:2]
			if paramType == 'GelatoShader':
				# ship shader version line
				continue
			if paramType == 'initcode':
				# end of tokens, done
				return self.type is not None
			if self.type is None:
				# first line tells us the shader type and name
				if (not shader_types.has_key(paramType)):
					if (self.verbose > 1):
						print 'Error: unknown shader type "%s" name "%s"' % (paramType, name)
					return False

				# shader and name type
				self.type = shader_types[paramType]
				self.name = name
				continue

			# parameter or global, or something we can ignore

			if paramType != 'param' and paramType != 'global' and paramType != 'oparam':
				continue

			# determine if input our output based on first character of the "value" argument position
			dataType = elements[2]
			paramName = elements[3]
			# or if paramName has a [ in it, it's an array param
			valCount = 1
			try:
				arrayStart = string.index(paramName, '[')
				valCount = int(paramName[arrayStart+1:-1])
				paramName = paramName[:arrayStart]
			except ValueError:
				# no array index
				arrayStart = -1

			isInput = False
			isOutput = False
			isGlobal = False
			if paramType == 'param': #  or paramType == 'global':
				isInput = True
			elif paramType == 'oparam': # or paramType == 'global':
				isOutput = True
			elif paramType == 'global':
				isGlobal = True

			paramData = elements[4:]

			if not literals.has_key(dataType):
				if self.verbose > 1:
					print 'Error: unknown parameter type "%s"' % elements
					continue
			lit = literals[dataType]

			par      = None

			# float
			baseParamData = paramData[0]
			if baseParamData[0] == '%':
				baseParamData = 'nodefault'
			elif valCount > 1:
				#print valCount
				baseParamData = string.join(paramData[:valCount], ' ')

			if valCount == 1:
				helpStr = '%s %s' % (str(lit), paramName)
			else:
				helpStr = '%s[%s] %s' % (str(lit), valCount, paramName)
			if (lit is literals.float):
				if baseParamData == 'nodefault':
					baseParamData = '0'
				par      = self.parameter(lit, baseParamData, valCount, helpStr)

			# string

			elif (lit is literals.string):
				if baseParamData == 'nodefault':
					baseParamData = ''
				par      = self.parameter(lit, paramData[0][1:-1], valCount, helpStr)

			# color, point, vector, normal

			elif ((lit is literals.color) or
				(lit is literals.point) or
				(lit is literals.vector) or
				(lit is literals.normal)):
					try:
						val = '%s %s %s' % tuple(paramData[:3])
					except:
						if baseParamData == 'nodefault':
							baseParamData = '0 0 0'
						val = baseParamData

					#print 'param %s: default value %s data = %s' % ( paramName, val, paramData )
					par      = self.parameter(lit, val, valCount, helpStr)

			# TODO matrix

			if ((paramName is None) or (par is None)):
				if (self.verbose > 1):
					print 'Error: unknown parameter "%s"' % elements
			else:
				# yes, globals will end up in both
				if isInput:
					self.parameters[paramName] = par
				if isOutput:
					self.outparams[paramName] = par
				if isGlobal:
					self.globals[paramName] = par

		fd.close()

		return True


####################################################
# Frappe data model
####################################################
class Frappe(object):
	def __init__(self):
		self.global_effects = {}
		self.mat_shader_groups = {}
		self.settings = {}
		self.exportfile = None
		self.user_transforms = {}
		self.geometry_sets = []
		self.Reset()

	def Reset(self):
		# WHY?
		if (WIN):
			texturefiles = '100'
		else:
			texturefiles = '1000'

		# material name -> shader
		self.global_effects = {}
		# material name -> [ [(shader name, shader), ...],  ... ]
		self.mat_shader_groups = {}
		self.exportfile = None
		self.settings = {
			'enable_anim':                False,
			'files_extensions':           False,
			'enable_binary':              False,
			'enable_split':               False,
			'enable_script_header':       False,
			'script_header':              '',
			'bucketorder':                2,
			'bucketsize_x':               32,
			'bucketsize_y':               32,
			'enable_error':               False,
			'errorfilename':              '>>gelato_log.txt',
			'enable_preview':             False,
			'preview_quality':            0.1,
			'enable_viewer':              True,
			'format':                     0,  # Null
			'data':                       0,  # RGB
			'compression':                1,  # ZIP
			'compression_shadow':         1,  # ZIP
			'shadow_maps':                0,
			'shadow_woo':                 0,
			'shadow_ray_traced':          0,
			'enable_dynamic':             False,
			'antialiasing_x':             4,
			'antialiasing_y':             4,
			'filter':                     0,  # Gaussian
			'filterwidth_x':              2.0,
			'filterwidth_y':              2.0,
			'gamma':                      1.0,
			'gain':                       1.0,
			'dither':                     0.5,
			'quantize_zero':              '0',
			'quantize_one':               '255',
			'quantize_min':               '0',
			'quantize_max':               '255',
			'all_double_sided':           False,
			'dup_verts':                  True,
			'enable_ray_traced':          False,
			'ray_traced_max_depth':       1,
			'ray_traced_shadow_bias':     0.01,
			'ray_traced_opaque_shadows':  1,
			'lights_factor':              50.0,
			'enable_key_fill_rim':        False,
			'enable_lights':              True,
			'enable_shaders':             True,
			'shadingquality':             1.0,
			'enable_ambient_occlusion':   False,
			'enable_bake_diffuse':        False,
			'enable_indirect_light':      False,
			'indirect_minsamples':        3,
			'enable_textures':            True,
			'enable_automipmap':          True,
			'enable_dof':                 False,
			'fstop':                      '4.0',
			'focallength':                '0.032',
			'dofquality':                 16,
			'enable_sky':                 True,
			'units_length':               0,
			'units_lengthscale':          '1.0',
			'limits_texturememory':       '20480',
			'limits_texturefiles':        texturefiles,
			#'path_shader':                path_shader,
			#'path_texture':               path_texture,
			#'path_inputs':                path_inputs,
			#'path_imageio':               path_imageio,
			#'path_generator':             path_generator,
			'pass_beauty':                True,
			'pass_shadows':               False,
			'pass_ambient_occlusion':     False,
			'pass_bake_diffuse':          False,
		}



####################################################
# PYG exporter
####################################################
class gelato_pyg(object):

	##########################
	# mask class for computing names when exporting a series of files
	class name_mask(object):
		__slots__ = ['pyg', 'name', 'ext', 'suffix']

		def __init__(self, pyg, name = '', ext = '', suffix = False):
			self.pyg    = pyg
			self.name   = name
			self.ext    = ext
			self.suffix = suffix

		def __str__(self):
			if (self.pyg.frame is None):
				return '%s%s%s' % (self.pyg.base, self.name, self.ext)
			else:
				if (self.suffix and self.pyg.files_extensions):
					# file.ext.NNN
					return '%s%s%s%s' % (self.pyg.base, self.name, self.ext, self.pyg.mask % self.pyg.frame)
				else:
					# file.NNN.ext
					return '%s%s%s%s' % (self.pyg.base, self.name, self.pyg.mask % self.pyg.frame, self.ext)

	##########################
	# data geometry class
	class data_geometry(object):
		__slots__ = ['materials', 'nverts', 'verts']

		def __init__(self, material, nvert):
			self.materials = [material]
			self.nverts    = [nvert]
			self.verts     = []

		def append(self, material, nvert):
			self.materials.append(material)
			self.nverts.append(nvert)

		def append_vert(self, vert):
			self.verts.append(vert)

	############################
    # texture data class
	class data_texture(object):
		__slots__ = ['name', 'file', 'mapping', 'extend', 'texco']

		def __init__(self, name, file, mapping, extend, texco):
			self.name    = name
			self.file    = file
			self.mapping = mapping
			self.extend  = extend
			self.texco   = texco

	"""
	Gelato class export.
	"""
	def __init__(self, filename):
		self.filename = filename

		self.PRECISION     = 6
		self.SCALEBIAS     = 0.1
		self.FACTORAMBIENT = 200

		self.SHADOWMAP_EXT = '.sm'
		self.TEXTURE_EXT   = '.tx'
		self.DIFFUSE_EXT   = '.sdb'

		if (sys.byteorder == 'little'):
			self.BINARY_INT   = 0200
			self.BINARY_FLOAT = 0202
		else:
			self.BINARY_INT   = 0201
			self.BINARY_FLOAT = 0203

		# FIXME
		self.convert_extend = dict([
			(Blender.Texture.ExtendModes.REPEAT,   'periodic'),
			(Blender.Texture.ExtendModes.CLIP,     'black'),
			(Blender.Texture.ExtendModes.CLIPCUBE, 'mirror'),
			(Blender.Texture.ExtendModes.EXTEND,   'clamp'),
#			(Blender.Texture.ExtendModes.CHECKER,  'periodic'),
		])

		self.default_geometrysets = []
		self.active_lights = []
		self.all_lights = []


	def generate_instance_name(self, name, ext = '', prefix = '', postfix = '', directory = True, noframe = False):
		if (directory):
			d = self.directory
		else:
			d = ''

		if (self.instance is None):
			if (noframe or self.frame is None):
				return os.path.join(d, '%s%s%s%s' % (prefix, name, postfix, ext))
			else:
				return os.path.join(d, '%s%s%s%s%s' % (prefix, name, postfix, self.pyg.mask % self.frame, ext))
		else:
			if (noframe or self.frame is None):
				return os.path.join(d, '%s%s%s-%s%s' % (prefix, name, postfix, self.instance, ext))
			else:
				return os.path.join(d, '%s%s%s-%s%s%s' % (prefix, name, postfix, self.instance, self.pyg.mask % self.frame, ext))


	def generate_split_name(self, name, prefix, i, n):
		if (self.frame is None):
			if (n <= 1):
				fname = '%s_%s_%s%s' % (self.base, prefix, name, self.ext)
			else:
				fname = '%s_%s_%s-%s%s' % (self.base, prefix, name, i, self.ext)
		else:
			if (n <= 1):
				fname = '%s_%s_%s%s%s' % (self.base, prefix, name, self.mask % self.frame, self.ext)
			else:
				fname = '%s_%s_%s-%s%s%s' % (self.base, prefix, name, i, self.mask % self.frame, self.ext)
		fname = relative_filename(str(fname), str(self.filename))
		return fname


	def object_name(self, name):
		if (self.instance is None):
			return name
		return self.generate_instance_name(name, prefix = '__', ext = '__', directory = False, noframe = True)


	def camera_shadow_name(self, name):
		return self.generate_instance_name(name, prefix = '__shadow_', ext = '__', directory = False, noframe = True)


	def file_shadow_name(self, name):
		return self.generate_instance_name(space2underscore(name), self.SHADOWMAP_EXT, self.base + '_shadow_')


	def file_diffuse_name(self, name):
		return self.generate_instance_name(space2underscore(name), self.DIFFUSE_EXT, self.base + '_diffuse_')


	def file_object_name(self, name, i, n):
		return self.generate_split_name(space2underscore(name), 'object', i, n)


	def file_output_pass(self):
		if (self.npasses <= 1):
			if (self.pass_name == 'Ambient Occlusion'):
				return str(self.filename_ambient_occlusion)
			return str(self.filename)

		if (self.pass_name == 'Beauty'):
			return str(self.filename_beauty)
		elif (self.pass_name == 'Shadow'):
			return str(self.filename_shadows)
		elif (self.pass_name == 'Ambient Occlusion'):
			return str(self.filename_ambient_occlusion)
		elif (self.pass_name == 'Bake Diffuse'):
			return str(self.filename_bake_diffuse)


	def write_matrix(self, matrix):
		"""
		Write 16 elements of matrix.
		"""
		self.file.write('(%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)\n' % (
			matrix[0][0], matrix[0][1], matrix[0][2], matrix[0][3],
			matrix[1][0], matrix[1][1], matrix[1][2], matrix[1][3],
			matrix[2][0], matrix[2][1], matrix[2][2], matrix[2][3],
			matrix[3][0], matrix[3][1], matrix[3][2], matrix[3][3]))


	def write_set_transform(self, matrix):
		self.file.write('SetTransform ')
		self.write_matrix(matrix)


	def write_append_transform(self, matrix):
		self.file.write('AppendTransform ')
		self.write_matrix(matrix)


	def write_translation(self, matrix):
		trans  = matrix.translationPart()
		self.file.write('Translate (%s, %s, %s)\n' %
			(trans.x, trans.y, trans.z))


	def write_move_scale_rotate(self, matrix):
		trans = matrix.translationPart()
		scale = matrix.scalePart()
		euler = matrix.toEuler()

		if ((trans.x != 0.0) or (trans.y != 0.0) or (trans.z != 0.0)):
			self.file.write('Translate (%s, %s, %s)\n' %
				(trans.x, trans.y, trans.z))

		self.file.write('Scale (%s, %s, %s)\n' %
			(scale.x, scale.y, -scale.z))

		if (euler.z != 0.0):
			self.file.write('Rotate (%s, 0, 0, 1)\n' %
				euler.z)

		if (euler.y != 0.0):
			self.file.write('Rotate (%s, 0, 1, 0)\n' %
				-euler.y)

		if (euler.x != 0.0):
			self.file.write('Rotate (%s, 1, 0, 0)\n' %
				-euler.x)


	def write_array(self, wfile, array, prefix = None, ascii = False):
		l = len(array)
		if (l == 0):
			return

		if (prefix):
			wfile.write(prefix)

		ty = type(array[0])

		if (self.enable_binary and not ascii):
			if (ty is int):
				wfile.write(struct.pack('=BI', self.BINARY_INT, l))
				for i in array:
					wfile.write(struct.pack('=i', i))
			elif (ty is float):
				wfile.write(struct.pack('=BI', self.BINARY_FLOAT, l))
				for f in array:
					try:
						s = struct.pack('=f', f)
					except:
						s = struct.pack('=f', 0.0)
					wfile.write(s)
		else:
			iarray = iter(array)
			if (ty is int):
				wfile.write('(%s' % iarray.next())
				for i in iarray:
					wfile.write(',%s' % i)
				wfile.write(')')
			elif (ty is float):
				wfile.write('(%s' % round(iarray.next(), self.PRECISION))
				for f in iarray:
					wfile.write(',%s' % round(f, self.PRECISION))
				wfile.write(')')


	def write_shadow_name(self, name = None):
		shadowname = None
		if (name and (self.shadow_maps or self.shadow_woo)):
			shadowname = self.file_shadow_name(name)
		elif (self.shadow_ray_traced):
			shadowname = 'shadows'

		if (shadowname):
			self.file.write('Parameter ("string shadowname", "%s")\n' %
				shadowname)


	def write_ray_traced(self):
		self.file.write('Attribute ("int ray:maxdepth", %d)\n' %
			self.ray_traced_max_depth)

		if (self.shadow_ray_traced):
			self.file.write('Attribute ("float shadow:bias", %s)\n' %
				round(self.ray_traced_shadow_bias, self.PRECISION))

			self.file.write('Attribute ("int ray:opaqueshadows", %d)\n' %
				self.ray_traced_opaque_shadows)

		self.file.write('Attribute ("string geometryset", "+reflection")\n')
		self.file.write('Attribute ("string geometryset", "+refraction")\n')


	def write_shadow_ray_traced(self):
		self.file.write('\nAttribute ("string geometryset", "+shadows")\n')


	def write_key_fill_rim(self):
		self.file.write('\nInput ("cameralights.pyg")\n')


	def write_ambient_occlusion_pass1(self):
		global frappe

		if (not self.format):
			raise GelatoError, 'No output format'

		shader_ambient_occlusion = frappe.global_effects['ambient_occlusion']
		if (shader_ambient_occlusion):
			self.file.write('\nAttribute ("string geometryset", "+%s")\n' %
				shader_ambient_occlusion['occlusionname'])
			self.default_geometrysets.append(shader_ambient_occlusion['occlusionname'])

			self.write_shader(shader_ambient_occlusion)
		else:
			self.file.write('\nAttribute ("string geometryset", "+localocclusion")\n')
			self.file.write('Shader ("surface", "ambocclude", "string occlusionname", "localocclusion")\n')
			self.default_geometrysets.append('localocclusion')


	def write_ambient_occlusion_pass2(self):
		global frappe

		if (not self.format):
			raise GelatoError, 'No output format'

		output = str(self.output_ambient_occlusion_tx)

		self.file.write('\n')

		shader_environment_light = frappe.global_effects['environment_light']
		if (shader_environment_light):
			shader_environment_light['occlusionmap'] = output

			shader_ambient_occlusion = frappe.global_effects['ambient_occlusion']
			if (shader_ambient_occlusion):
				shader_environment_light['occlusionname'] = shader_ambient_occlusion['occlusionname']

			self.write_shader(shader_environment_light)
		else:
			self.file.write('Light ("__envlight_pass2__", "envlight", "string occlusionmap", "%s" )\n' %
				output)


	def write_indirect_light(self):
		global frappe

		self.file.write('\nAttribute ("string geometryset", "+indirect")\n')
		self.file.write('Attribute ("int indirect:minsamples", %d)\n' %
			self.indirect_minsamples)
		self.default_geometrysets.append('indirect')

		shader_indirect_light = frappe.global_effects['indirect_light']
		if (shader_indirect_light):
			self.write_shader(shader_indirect_light)
		else:
			self.file.write('Light ("__indirectlight__", "indirectlight")\n')


	def write_background_color(self):
		if (not self.world):
			return

		# the horizon color
		col = self.world.hor

		self.file.write('\nPushAttributes ()\n')

		self.file.write('Attribute ("color C", (%s, %s, %s))\n' % (
			round(col[0], self.PRECISION),
			round(col[1], self.PRECISION),
			round(col[2], self.PRECISION)))

		self.file.write('Shader ("surface", "constant")\n')
		self.file.write('Input ("backplane.pyg")\n')

		self.file.write('PopAttributes ()\n')


	def write_ambientlight(self):
		if (not self.world):
			return

		# the ambient color
		col = self.world.amb

		if (col != [0.0, 0.0, 0.0]):
			self.file.write('\nLight ("%s", "ambientlight", '
					'"float intensity", %s, '
					'"color lightcolor", (%s, %s, %s))\n' % (
				self.world.name,
				self.lights_factor / self.FACTORAMBIENT,
				round(col[0], self.PRECISION),
				round(col[1], self.PRECISION),
				round(col[2], self.PRECISION)))
		self.active_lights.append(self.world.name)
		self.all_lights.append(self.world.name)


	def write_pointlight(self, obj, lamp, matrix):
		name = obj.name

		self.file.write('\nPushTransform ()\n')

		self.write_translation(matrix)
		self.write_shadow_name(name)

		self.file.write('Light ("%s", "pointlight", '
				'"float falloff", 2.0, '
				'"float intensity", %s, '
				'"color lightcolor", (%s, %s, %s))\n' % (
			self.object_name(name),
			lamp.getEnergy() * self.lights_factor,
			lamp.R, lamp.G, lamp.B))

		self.file.write('PopTransform ()\n')
		self.active_lights.append(self.object_name(name))
		self.all_lights.append(self.object_name(name))


	def write_distantlight(self, obj, lamp, matrix):
		name = obj.name

		self.file.write('\nPushTransform ()\n')

		self.write_move_scale_rotate(matrix)
		self.write_shadow_name(name)

		self.file.write('Light ("%s", "distantlight", '
				'"float intensity", %s, '
				'"color lightcolor", (%s, %s, %s), '
				'"float shadowsamples", %s, '
				'"float shadowbias", %s)\n' % (
			self.object_name(name),
			lamp.getEnergy() * self.lights_factor,
			lamp.R, lamp.G, lamp.B,
			float(lamp.samples),
			lamp.bias * self.SCALEBIAS))

		self.file.write('PopTransform ()\n')
		self.active_lights.append(self.object_name(name))
		self.all_lights.append(self.object_name(name))


	def write_spotlight(self, obj, lamp, matrix):
		name = obj.name

		self.file.write('\nPushTransform ()\n')

		self.write_move_scale_rotate(matrix)
		self.write_shadow_name(name)

		self.file.write('Light ("%s", "spotlight", '
				'"float falloff", 2.0, '
				'"float intensity", %s, '
				'"color lightcolor", (%s, %s, %s), '
				'"float coneangle", %s, '
				'"float conedeltaangle", %s, '
				'"float shadowsamples", %s, '
				'"float shadowbias", %s)\n' % (
			self.object_name(name),
			lamp.getEnergy() * self.lights_factor,
			lamp.R, lamp.G, lamp.B,
			math.radians(lamp.spotSize / 2.0),
			math.radians(lamp.spotBlend * lamp.spotSize / 4.0),
			float(lamp.samples),
			lamp.bias * self.SCALEBIAS))

		self.file.write('PopTransform ()\n')
		self.active_lights.append(self.object_name(name))
		self.all_lights.append(self.object_name(name))


	def write_device(self, output_name, driver, data, camera_name):
		self.file.write('\n')
		output = output_name

		if (driver != 'null'):

			self.file.write('Parameter ("string filter", "%s")\n' %
				self.filter)

			self.file.write('Parameter ("float[2] filterwidth", (%s, %s))\n' %
				(round(self.filterwidth_x, self.PRECISION), round(self.filterwidth_y, self.PRECISION)))

			self.file.write('Parameter ("float gain", %s)\n' %
				round(self.gain, self.PRECISION))

			self.file.write('Parameter ("float gamma", %s)\n' %
				round(self.gamma, self.PRECISION))

			if ((driver != 'iv') and self.compression):
				self.file.write('Parameter ("string compression", "%s")\n' %
					self.compression)

			if (driver != 'OpenEXR'):
				self.file.write('Parameter ("float dither", %s)\n' %
					self.dither)

				self.file.write('Parameter ("int[4] quantize", (%s, %s, %s, %s))\n' % (
					int(self.quantize_zero),
					int(self.quantize_one),
					int(self.quantize_min),
					int(self.quantize_max)))
			else:
				self.file.write('Parameter ("string software", "Frappe %s")\n' %
					__version__)

			if (driver == 'iv'):
				self.file.write('Parameter ("int remote", 1)\n')
				output = output_name
			else:
				output = relative_filename(str(output_name), str(self.filename))

		self.file.write('Output ("%s", "%s", "%s", "%s")\n' %
			(output, driver, data, camera_name))


	def write_camera(self, obj):
		type = obj.type
		if (type != 'Camera'):
			return

		name    = obj.name
		matrix  = obj.getMatrix()
		cam     = Blender.Camera.Get(obj.getData().name)

		ratio_x = self.context.aspectRatioX()
		ratio_y = self.context.aspectRatioY()
		ratio   = float(ratio_y) / float(ratio_x)

		self.file.write('\nPushTransform ()\n')
		self.file.write('PushAttributes ()\n')

		self.write_move_scale_rotate(matrix)

		self.file.write('Attribute ("float near", %s)\n' %
			round(cam.clipStart, self.PRECISION))

		self.file.write('Attribute ("float far", %s)\n' %
			round(cam.clipEnd, self.PRECISION))

		# perspective camera

		if (cam.type == 'persp'):

			if (ratio_x != ratio_y):
				aspx = self.sizex / self.sizey
				aspy = ratio
				self.file.write('Attribute ("float[4] screen", (%s, %s, %s, %s))\n' %
					(-aspx, aspx, -aspy, aspy))

			if (self.sizex > self.sizey):
				fac = self.sizey / self.sizex
			else:
				fac = 1.0

			fov = math.degrees(2.0 *  math.atan2(16.0 * fac, cam.lens))

			self.file.write('Attribute ("string projection", "perspective")\n')
			self.file.write('Attribute ("float fov", %s)\n' %
				round(fov, self.PRECISION))

		# orthographic camera

		elif (cam.type == 'ortho'):

			aspx = cam.scale / 2.0
			aspy = aspx * self.sizey / self.sizex * ratio

			self.file.write('Attribute ("string projection", "orthographic")\n')
			self.file.write('Attribute ("float[4] screen", (%s, %s, %s, %s))\n' %
				(-aspx, aspx, -aspy, aspy))

		else:
			raise GelatoError, 'Invalid camera type "%s"' % cam.type

		self.file.write('Attribute ("float pixelaspect", %s)\n' %
			(1.0 / ratio))

		if (self.enable_dof and self.pass_name == 'Beauty'):
			self.file.write('Attribute ("int dofquality", %d)\n' %
				self.dofquality)

			if (self.fstop):
				self.file.write('Attribute ("float fstop", %s)\n' %
					float(self.fstop))

			if (self.focallength):
				self.file.write('Attribute ("float focallength", %s)\n' %
					float(self.focallength))

			self.file.write('Attribute ("float focaldistance", %s)\n' %
				cam.dofDist)

		self.file.write('Camera ("%s")\n' %
			name)

		self.file.write('PopAttributes ()\n')
		self.file.write('PopTransform ()\n')


	def write_camera_light(self, obj, lamp, name, matrix):
		self.file.write('\nPushTransform ()\n')

		self.write_move_scale_rotate(matrix)

		self.file.write('Camera ("%s", '
				'"int[2] resolution", (%d, %d), '
				'"int[2] spatialquality", (%d, %d), '
				'"string projection", "perspective", '
				'"float fov", %s, '
				'"float near", %s, '
				'"float far", %s)\n' % (
			self.camera_shadow_name(name),
			lamp.bufferSize, lamp.bufferSize,
			lamp.samples, lamp.samples,
			lamp.spotSize,
			lamp.clipStart,
			lamp.clipEnd))

		self.file.write('PopTransform ()\n')

		if (self.enable_dynamic):
			self.file.write('Parameter ("int dynamic", 1)\n')

		if (self.shadow_woo):
			shadow_data = 'avgz'
		else:
			shadow_data = 'z'

		self.file.write('Output ("%s", '
				'"shadow", "%s", "%s", '
				'"string compression", "%s", '
				'"string filter", "min", '
				'"float[2] filterwidth", (1.0, 1.0), '
				'"float dither", 0.0, '
				'"int[4] quantize", (0, 0, 0, 0))\n' % (
			self.file_shadow_name(name),
			shadow_data,
			self.camera_shadow_name(name),
			self.compression_shadow))


	def write_script(self, name):
		if (name is None):
			return

		try:
			txt = Blender.Text.Get(name)
		except:
			if (self.verbose > 1):
				print 'Error: invalid script "%s"' % name
			return

		if ((txt is None) or (txt.nlines == 0)):
			return

		self.file.write('\nInput ("pyg << ')

		for line in txt.asLines():
			if (line):
				self.file.write(escape_quote(line))
				self.file.write('\\n')

		self.file.write('")\n')


	def write_mesh(self, name, i, n, single_sided, interpolation, nverts,\
			verts, points, normals = [], vertexcolor = [], holes = [], s = [], t = []):

		if (single_sided):
			self.file.write('Attribute ("int twosided", 0)\n')

		if (self.enable_split):
			fobj_name = self.file_object_name(name, i, n)

			self.file.write('Input ("%s")\n' % fobj_name)

			if (fobj_name not in self.fileobject_memo):

				basepath = os.path.dirname(str(self.filename))
				fname = os.path.normpath(os.path.join(basepath, fobj_name))
				wfile = open(fname, 'w')
				self.fileobject_memo.append(fobj_name)

				if (self.verbose > 1):
					print 'Info: exporting object file "%s"' % fname
			else:
				return
		else:
			wfile = self.file

		try:
			wfile.write('Mesh ("%s"' % interpolation)

			self.write_array(wfile, nverts,      ',', True)
			self.write_array(wfile, verts,       ',', True)
			self.write_array(wfile, points,      ',"vertex point P",')
			self.write_array(wfile, normals,     ',"vertex normal N",')
			self.write_array(wfile, vertexcolor, ',"vertex color C",')
			self.write_array(wfile, s,           ',"linear float s",')
			self.write_array(wfile, t,           ',"linear float t",')
			self.write_array(wfile, holes,       ',"int[%d] holes",' % len(holes), True)

			wfile.write(')\n')

		finally:
			if (self.enable_split):
				wfile.close()


	def write_shader(self, sd):
		if (sd.type is None or (not sd.name)):
			if (self.verbose >= 1):
				print 'Error: null shader'
			return

		slist = []
		for name, par in sd.parameters.iteritems():

			# skip if no change

			if (not par.change):
				continue

			ty = par.type

			# float

			if (ty is literals.float):
				try:
					self.file.write('Parameter ("float %s", %s)\n' %
						(name, float(par.value)))
				except ValueError:
					if (self.verbose >= 1):
						print 'Error: parameter not valid "%s"' % par.value
					continue

			# string

			elif (ty is literals.string):
				self.file.write('Parameter ("string %s", "%s")\n' %
					(name, par.value.strip()))

			# color, point vector, normal

			elif ((ty is literals.color) or
				(ty is literals.point) or
				(ty is literals.vector) or
				(ty is literals.normal)):
					val = par.value.strip()
					lpar = val.split(' ')
					l = len(lpar)
					if (l == 1):
						self.file.write('Parameter ("%s %s", %s)\n' %
							(str(ty), name, val))
					elif (l == 3):
						self.file.write('Parameter ("%s %s", (%s))\n' %
							(str(ty), name, ', '.join(lpar)))
					else:
						if (self.verbose >= 1):
							print 'Error: parameter not valid "%s"' % par.value
						continue

			# TODO matrix

			else:
				if (self.verbose > 1):
					print 'Error: unknown parameter "%s"' % name

		ty = sd.type

		if ((ty is shader_types.surface) or
			(ty is shader_types.displacement) or
			(ty is shader_types.volume) or
			(ty is shader_types.generic)):
				# Shader: surface, displacement, volume, generic
				if sd.layer is None:
					self.file.write('Shader ("%s", "%s")\n' % (str(ty), sd.name))
				else:
					self.file.write('Shader ("%s", "%s", "%s")\n' % (str(ty), sd.name, sd.layer))
		elif (ty is shader_types.light):
			# Light
			if sd.layer is None:
				self.file.write('Light ("%s", "%s")\n' % (sd.nameid, sd.name))
			else:
				self.file.write('Light ("%s", "%s", "%s")\n' % (sd.nameid, sd.name, sd.layer))
		else:
			if (self.verbose >= 1):
				print 'Error: unknown type shader "%s"' % str(ty)


	def camera_shadows(self, obj, matrices):
		type = obj.type
		if (type != 'Lamp'):
			return

		name = obj.name
		lamp = Blender.Lamp.Get(obj.getData().name)

		if len(matrices) == 1:
			mat = matrices[0]
		else:
			raise GelatoError, sys._getframe(0).f_code.co_name + ' invalid number of items in input matrices'

		ltype = lamp.type
		if (ltype is Blender.Lamp.Types.Spot or
			ltype is Blender.Lamp.Types.Sun or
			ltype is Blender.Lamp.Types.Lamp):
				self.write_camera_light(obj, lamp, name, mat)


	def light(self, obj, matrices):
		type = obj.type
		if (type != 'Lamp'):
			return

		name = obj.name
		lamp = Blender.Lamp.Get(obj.getData().name)

		if len(matrices) == 1:
			mat = matrices[0]
		else:
			raise GelatoError, sys._getframe(0).f_code.co_name + ' invalid number of items in input matrices'

		ltype = lamp.type
		if (ltype is Blender.Lamp.Types.Lamp):
			self.write_pointlight(obj, lamp, mat)
		elif (ltype is Blender.Lamp.Types.Sun):
			self.write_distantlight(obj, lamp, mat)
		elif (ltype is Blender.Lamp.Types.Spot):
			self.write_spotlight(obj, lamp, mat)


	def mesh(self, obj, matrices):
		global frappe
		global mesh_errors

		type = obj.type
		if ((type != 'Mesh') and (type != 'Surf')):
			return

		obj.makeDisplayList()
		name = obj.getData(True, True)

		try:
			#mesh = Blender.NMesh.GetRawFromObject(name)
			mesh = Blender.Mesh.New()
			mesh.getFromObject(obj)
			# mesh = Blender.Mesh.Get(name)
		except:
			if (self.verbose > 0):
				#sys.excepthook(*sys.exc_info())
				print 'ERROR: Mesh %s not found' % repr(name)
			return

		nfaces = len(mesh.faces)
		if (nfaces == 0):
			return

		# single sided face

		single_sided = False
		if (not (self.all_double_sided or (mesh.mode & Blender.Mesh.Modes.TWOSIDED))):
			single_sided = True

		# vertex colors

		#vtcolor = mesh.hasVertexColours()
		vtcolor = mesh.vertexColors

		# face UV

		#faceuv = mesh.hasFaceUV()
		faceuv = mesh.faceUV

		# get properties

		catmull_clark = get_property_bool(obj, 'gelato:catmull_clark')
		bake_diffuse  = get_property_bool(obj, 'gelato:bake_diffuse')

		# interpolation type

		if (catmull_clark):
			interpolation = 'catmull-clark'
		else:
			interpolation = 'linear'

		# if NURBS smooth surface

		if (type == 'Surf'):
			smooth = True
		else:
			smooth = False

		# geometry

		if (vtcolor):
			nlist_col = range(len(mesh.verts))

		nverts = []
		verts = []
		db_geometry = {}

		errorcount = 0
		for i, face in enumerate(mesh.faces):
			if (face.smooth):
				smooth = True

			l = len(face.v)
			nverts.append(l)
			mat_idx = face.mat
			if len(mesh.materials) <= mat_idx:
				errorcount = errorcount + 1
				if mesh_errors.has_key(name):
					mesh_errors[name] = mesh_errors[name] + 1
				else:
					mesh_errors[name] = 1
					print 'WARNING: mesh %s has invalid material index %s on face materials=%s' % (name, mat_idx, repr(mesh.materials))

			if (db_geometry.has_key(mat_idx)):
				db_geometry[mat_idx].append(i, l)
			else:
				db_geometry[mat_idx] = self.data_geometry(i, l)

			for vert in face.v:
				verts.append(vert.index)
				db_geometry[mat_idx].append_vert(vert.index)

			if (vtcolor):
				for j in xrange(len(face.v)):
					c = face.col[j]
					nlist_col[face.v[j].index] = [c.r, c.g, c.b]

		# points

		points = []
		nlist_nor = []
		for vert in mesh.verts:
			nlist_nor.append(vert.no)
			points.extend([vert.co.x, vert.co.y, vert.co.z])

		# normals

		normals = []
		if (smooth and (not catmull_clark)):
			for face in mesh.faces:
				if (face.smooth):
					continue
				for vert in face.v:
					nlist_nor[vert.index] = face.no

			for nor in nlist_nor:
				normals.extend([nor[0], nor[1], nor[2]])

		# vertex color

		vertexcolor = []
		if (vtcolor):
			for c in nlist_col:
				try:
					vertexcolor.extend([c[0]/255.0, c[1]/255.0, c[2]/255.0])
				except:
					vertexcolor.extend([0.0, 0.0, 0.0])

		self.file.write('\n# Mesh %s\n' % self.object_name(name))

		ignoreLights = []
		frappeProps = get_frappe_properties(obj)
		for propName, prop in frappeProps.iteritems():
			if propName[:12] == 'ignorelight ':
				lightName = propName[12:]
				if not lightName in self.all_lights:
					print 'ERROR: unknown light %s' % lightName
					continue
				if prop.getData():
					ignoreLights.append(lightName)
					if lightName in self.active_lights:
						# turn off the light
						self.file.write('LightSwitch ("%s", False)\n' % lightName)
						self.active_lights.remove(lightName)

		for lightName in self.all_lights:
			if not lightName in ignoreLights and not lightName in self.active_lights:
				self.file.write('LightSwitch ("%s", True)\n' % lightName)
				self.active_lights.append(lightName)

		self.file.write('PushAttributes ()\n')

		newGeometrySets = []
		newGeometrySets.extend(self.default_geometrysets)
		for propName, prop in frappeProps.iteritems():
			if propName[:12] == 'geometryset ':
				setName = propName[12:]
				if prop.getData():
					newGeometrySets.append(setName)
					if not setName in self.default_geometrysets:
						self.file.write('Attribute ("string geometryset", "+%s")\n' % setName)
				elif setName in newGeometrySets:
					self.file.write('Attribute ("string geometryset", "-%s")\n' % setName)
					newGeometrySets.remove(setName)

		self.file.write('Attribute ("string name", "%s")\n' %
			self.object_name(name))

		# transform

		l = len(matrices)
		if l == 1:
			self.write_set_transform(matrices[0])
		elif l == 2:
			# motion blur
			# TODO
			raise GelatoError, sys.getframe(0).f_code.co_name + ' motion blur not implemented'
		else:
			raise GelatoError, sys.getframe(0).f_code.co_name + ' invalid number of items in input matrices'

		if (bake_diffuse and self.pass_name == 'Bake Diffuse'):
			self.file.write('Attribute ("int cull:occlusion", 0)\n')
			self.file.write('Attribute ("int dice:rasterorient", 0)\n')

			shader_bake_diffuse = frappe.global_effects['bake_diffuse']
			file_name = str(self.file_diffuse_name(name))

			if (shader_bake_diffuse is not None):
				sd = copy.deepcopy(shader_bake_diffuse)
				sd['filename'] = relative_filename(file_name, str(self.filename))
				self.write_shader(sd)
			else:
				self.file.write('Shader ("surface", "bakediffuse", '
						'"string filename", "%s", '
						'"float weightarea", 1, '
						'"float interpolate", 1)\n' %
							relative_filename(file_name, str(self.filename)))

		if (mesh.materials and (not self.pass_name == 'Ambient Occlusion') and (not self.pass_name == 'Shadow')):

			# materials

			multiple_mat = len(mesh.materials) > 1
			if (multiple_mat and catmull_clark):
				set_mat = set(range(nfaces))

			ngeo = len(db_geometry)

			for mat_idx, geo in db_geometry.iteritems():

				try:
					mat = mesh.materials[mat_idx]
				except:
					print 'WARNING: mesh has no material at index %s [of %s]' % (mat_idx, len(db_geometry))
					continue

				if (not mat):
					print 'WARNING: mesh material at index %s is none' % mat_idx
					continue

				self.file.write('PushAttributes ()\n')

				flags = mat.getMode()

				# vertex color

				if (not (flags & Blender.Material.Modes.VCOL_PAINT)):
					vertexcolor = []

				# multiple materials on a single mesh

				if (multiple_mat and catmull_clark):
					holes = list(set_mat - set(geo.materials))
				else:
					holes = []

				# color

				self.file.write('Attribute ("color C", (%s, %s, %s))\n' % (
					round(mat.R, self.PRECISION),
					round(mat.G, self.PRECISION),
					round(mat.B, self.PRECISION)))

				# alpha

				alpha = mat.alpha
				if (alpha < 1.0):
					alpha = round(alpha, self.PRECISION)
					self.file.write('Attribute ("color opacity", (%s, %s, %s))\n' %
						(alpha, alpha, alpha))

				# texture files (FIXME)

				textures_color = []
				list_tex = mat.getTextures()

				if (list_tex):
					for mtex in list_tex:
						if (not mtex):
							continue
						if (mtex.tex.type is Blender.Texture.Types.IMAGE):
							image = mtex.tex.getImage()
							if (not image):
								continue
							filename = Blender.sys.expandpath(image.getFilename())
							if (mtex.mapto & Blender.Texture.MapTo.COL):
								textures_color.append(self.data_texture(mtex.tex.getName(),
									filename,
									mtex.mapping,
									mtex.tex.extend,
									mtex.texco))

				# UV coordinates

				tex_s = []
				tex_t = []
				if (faceuv):
					for faceindex in geo.materials:
						face = mesh.faces[faceindex]
						for uv in face.uv:
							tex_s.append(round(uv[0], self.PRECISION))
							tex_t.append(round(1.0 - uv[1], self.PRECISION))
				#print 'Size of uv list: ', len(tex_s)

				# shader surface

				if (self.verbose > 0):
					self.file.write('## Material: "%s"\n' % mat.name)

				# JM - handle displacement shaders separately
				if (self.enable_shaders and not self.enable_debug_shaders):

					if ((not (flags & Blender.Material.Modes.SHADELESS)) and not (bake_diffuse and self.pass_name == 'Bake Diffuse')):
						matShaders = frappe.mat_shader_groups.get(mat.name)
						#print 'write material %s' % mat.name
						if (matShaders is not None):
							for shaderGroup in matShaders:
								#print 'write shader group %s' % shaderGroup.name

								shaderList = []

								for shaderData in shaderGroup.shaders:
									if shaderData is not None:
										#print 'process shader %s / %s' % (shaderData.layer, shaderData.name)
										sd = copy.deepcopy(shaderData)
										if (sd is not None):
											if (bake_diffuse and self.enable_bake_diffuse and self.pass_name == 'Beauty' and sd.enable_sss):
												try:
													file_name = str(self.file_diffuse_name(name))
													sd[sd.sss_param] = relative_filename(file_name, str(self.filename))
												except:
													if (self.verbose > 0):
														sys.excepthook(*sys.exc_info())
											#self.file.write(str(sd))
											shaderList.append(sd)
										else:
											#self.file.write('Shader ("surface", "plastic")\n')
											#shaderList.append('Shader ("surface", "plastic")\n')
											raise GelatoError, "No shader specified!"
									else:
										print 'WARNING: None shader found in group'

								if len(shaderList) > 0:
									self.file.write('## Shader group %s\n' % shaderGroup.name)
									self.file.write('ShaderGroupBegin()\n')
									#if (shaderGroup.type is shader_types.surface and self.enable_textures and textures_color and self.pass_name == 'Beauty'):
									#	for ftex in textures_color:
									#		if (self.verbose > 0):
									#			self.file.write('## Texture: "%s"\n' % ftex.name)
									#		self.file.write('Shader ("surface", "pretexture", "string texturename", "%s", "string wrap", "%s")\n' %
									#			(fix_file_name(ftex.file), self.convert_extend[ftex.extend]))

									for sd in shaderList:
										#print 'write shader %s / %s' % (sd.layer, sd.name)
										self.write_shader(sd)
									for targetLayer, connections in shaderGroup.connections.iteritems():
										# will be a dict of { target parameter : ( source layer, source parameter )}
										for paramName, sourceData in connections.iteritems():
											sourceLayer, sourceParam = sourceData
											self.file.write('ConnectShaders ("%s", "%s", "%s", "%s")\n' % (sourceLayer, sourceParam, targetLayer, paramName))
									self.file.write('ShaderGroupEnd()\n')

				# FIXME MA_ONLYCAST ???
				if (flags & 0x2000):
					self.file.write('Attribute ("string geometryset", "-camera")\n')

				if (not (flags & Blender.Material.Modes.TRACEABLE)):
					self.file.write('Attribute ("string geometryset", "-shadows")\n')

				if (flags & Blender.Material.Modes.TRANSPSHADOW):
					self.file.write('Attribute ("int ray:opaqueshadows", 0)\n')

				# textures UV coordinates

				mesh_s = tex_s
				mesh_t = tex_t

				# geometry

				if (catmull_clark):
					self.write_mesh(name, i, ngeo, single_sided, interpolation, nverts,
						verts,     points, normals, vertexcolor, holes, mesh_s, mesh_t)
				else:
					self.write_mesh(name, i, ngeo, single_sided, interpolation, geo.nverts,
						geo.verts, points, normals, vertexcolor, [],    mesh_s, mesh_t)

				self.file.write('PopAttributes ()\n')
		else:
			self.write_mesh(name, 0, 0, single_sided, interpolation, nverts, verts, points, normals)

		self.file.write('PopAttributes ()\n')


	def visible(self, obj):
		if ((obj.users > 1) and ((frozenset(obj.layers) & self.viewlayer) == frozenset())):
			if (self.verbose > 1):
				print 'Info: Object "%s" invisible' % obj.name
			return False
		return True


	def build(self, obj, method, mblur = False):
		if (not self.visible(obj)):
			return

		self.instance = None
		mb = mblur and self.pass_name == 'Beauty'

		func = getattr(self, method)

		if (self.dup_verts):
			try:
				# get duplicate object
				dupobjs = obj.DupObjects
			except:
				dupobjs = None

			if (dupobjs):
				self.instance = 0
				for dobj, mat in dupobjs:
					apply(func, (dobj, [mat]))
					#exec('self.%s(dobj, [mat])' % method)
					self.instance += 1
				return
			else:
				try:
					# skip object if DupObjects
					if (obj.parent and obj.parent.DupObjects):
						return
				except:
					pass

		matrices = [obj.matrix]

		apply(func, (obj, matrices))
		#exec('self.%s(obj, matrices)' % method)


	def cameras_shadows(self):
		for obj in self.scene.objects:
			self.build(obj, 'camera_shadows')


	def lights(self):
		bar = 'Lights ...'
		if (not ((self.frame is None) or (self.nframes is None))):
			bar += ' (%d/%d)' % (self.frame, self.nframes)

		self.write_ambientlight()
		Blender.Window.DrawProgressBar(0.0, bar)

		n = float(len(self.scene.objects))

		for i, obj in enumerate(self.scene.objects):
			self.build(obj, 'light')
			Blender.Window.DrawProgressBar(float(i) / n, bar)


	def geometries(self):
		bar = 'Geometries ...'
		if (not ((self.frame is None) or (self.nframes is None))):
			bar += ' (%d/%d)' % (self.frame, self.nframes)

		Blender.Window.DrawProgressBar(0.0, bar)

		n = float(len(self.scene.objects))
		for i, obj in enumerate(self.scene.objects):
			if (self.verbose > 1):
				print 'Info: Object "%s" type "%s"' % (obj.name, obj.type)
			self.build(obj, 'mesh')
			Blender.Window.DrawProgressBar(float(i) / n, bar)


	def write_head(self):
		"""
		Write pyg header.
		"""
		global frappe

		try:
			curcam = self.scene.objects.camera
		except:
			# try the deprecated method
			curcam  = self.scene.getCurrentCamera()

		try:
			self.camera_name = curcam.name
		except:
			raise GelatoError, 'No camera present'

		scale  = self.context.getRenderWinSize() / 100.0

		self.file.write('## Exported by Frappe %s\n##\n' % __version__)
		self.file.write(datetime.datetime.today().strftime('## Timestamp: %Y-%m-%d %H:%M:%S\n'))
		if USE_GETPASS:
			self.file.write('## User: %s\n' % getpass.getuser())
		self.file.write('## Platform: %s\n' % sys.platform)

		self.file.write('## Pass: %s\n' % self.pass_name)

		if (not ((self.frame is None) or (self.nframes is None))):
			self.file.write('## Frame: %d/%d\n' %
				(self.frame, self.nframes))

		self.file.write('\nAttribute ("int verbosity", %d)\n' %
			self.verbose)

		if (self.enable_error and self.errorfilename):
			self.file.write('Attribute ("string error:filename", "%s")\n' %
				fix_file_name(self.errorfilename))

		# TODO: relative paths?
		#if (self.path_shader):
		#	self.file.write('Attribute ("string path:shader", "%s:&")\n' %
		#		fix_file_name(fix_vars(self.path_shader)))

		#if (self.path_texture):
		#	self.file.write('Attribute ("string path:texture", "%s:&")\n' %
		#		fix_file_name(fix_vars(self.path_texture)))

		#if (self.path_inputs):
		#	self.file.write('Attribute ("string path:input", "%s:&")\n' %
		#		fix_file_name(fix_vars(self.path_inputs)))

		#if (self.path_imageio):
		#	self.file.write('Attribute ("string path:imageio", "%s:&")\n' %
		#		fix_file_name(fix_vars(self.path_imageio)))

		#if (self.path_generator):
		#	self.file.write('Attribute ("string path:generator", "%s:&")\n' %
		#		fix_file_name(fix_vars(self.path_generator)))

		self.file.write('Attribute ("int[2] resolution", (%d, %d))\n' %
			(int(self.sizex * scale), int(self.sizey * scale)))

		if (self.context.borderRender):
			self.file.write('Attribute ("float[4] crop", (%s, %s, %s, %s))\n' % (
				self.context.border[0],
				self.context.border[2],
				1.0 - self.context.border[3],
				1.0 - self.context.border[1]))
#			self.file.write('Attribute ("int rerender", 1)\n')

		self.file.write('Attribute ("int[2] spatialquality", (%d, %d))\n' %
			(self.antialiasing_x , self.antialiasing_y))

		if (self.enable_preview):
			self.file.write('Attribute ("float preview", %s)\n' %
				round(self.preview_quality, self.PRECISION))

		if (self.enable_shaders):
			self.file.write('Attribute ("float shadingquality", %s)\n' %
				round(self.shadingquality, self.PRECISION))

		self.file.write('Attribute ("int[2] limits:bucketsize", (%d, %d))\n' %
			(self.bucketsize_x , self.bucketsize_y))

		self.file.write('Attribute ("string bucketorder", "%s")\n' %
			self.bucketorder)

		self.file.write('Attribute ("string orientation", "outside")\n')
		self.file.write('Attribute ("int twosided", 1)\n')

		if (self.enable_textures):
			self.file.write('Attribute ("int limits:texturememory", %d)\n' %
				int(self.limits_texturememory))

			self.file.write('Attribute ("int limits:texturefiles", %d)\n' %
				int(self.limits_texturefiles))

			self.file.write('Attribute ("int texture:automipmap", %d)\n' %
				self.enable_automipmap)

		if (self.units_length):
			self.file.write('Attribute ("string units:length", "%s")\n' %
				self.units_length)

			self.file.write('Attribute ("float units:lengthscale", %s)\n' %
				round(float(self.units_lengthscale), self.PRECISION))

		if (self.enable_ray_traced):
			self.write_ray_traced()

		# displacement radius
		# TODO: allow the user to set the max radius instead
		# of hardcoding a value of 1
		self.file.write('Attribute ("float displace:maxradius", 1)')

		# output - pick a device depending on the pass being rendered
		if self.pass_name == 'Shadow':
			self.write_device(self.title, 'null', self.data_color, self.camera_name)
		elif self.pass_name == 'Beauty':
			if self.format:
				self.write_device(str(self.output_color), self.format, self.data_color, self.camera_name)
			else:
				print 'No output device, writing a null device for the beauty pass'
				self.write_device(self.title, 'null', self.data_color, self.camera_name)
			if (self.data_z):
				self.write_device(str(self.output_z), self.format, self.data_z, self.camera_name)
		elif self.pass_name == 'Bake Diffuse':
			self.write_device(self.title, 'null', self.data_color, self.camera_name)
		elif self.pass_name == 'Ambient Occlusion':
			if self.format:
				self.write_device(str(self.output_ambient_occlusion), self.format, self.data_color, self.camera_name)
			elif self.npasses > 1:
				raise GelatoError, 'No output format for ambient occlusion pass'
			else:
				self.write_device(self.title, 'null', self.data_color, self.camera_name)
		else:
			# unknown pass
			raise GelatoError, 'Unknown pass %s' % self.pass_name

		self.write_camera(curcam)

		if ((self.shadow_maps or self.shadow_woo) and
			((self.pass_name == 'Beauty' and self.enable_dynamic) or self.pass_name == 'Shadow')):
				self.cameras_shadows()

		for transName, trans in frappe.user_transforms.iteritems():
			yaw, pitch, roll, transX, transY, transZ, scaleX, scaleY, scaleZ = trans
			self.file.write('\nPushAttributes()\n')
			if yaw > 0.00001:
				self.file.write('Rotate(%s, 0, 0, 1)\n' % yaw)
			if pitch > 0.00001:
				self.file.write('Rotate(%s, 1, 0, 0)\n' % pitch)
			if roll > 0.00001:
				self.file.write('Rotate(%s, 0, 1, 0)\n' % roll)
			if (transX * transX + transY * transY + transZ * transZ) > 0.00001:
				self.file.write('Translate(%s, %s, %s)\n' % (transX, transY, transZ) )
			if ( (scaleX - 1.0) * (scaleX - 1.0) + (scaleY - 1.0) * (scaleY - 1.0) + (scaleZ - 1.0) * (scaleZ - 1.0) ) > 0.00001:
				self.file.write('Scale(%s, %s, %s)\n' % (scaleX, scaleY, scaleZ) )
			self.file.write('SaveAttributes("%s", "transform")\n' % transName)
			self.file.write('PopAttributes()\n')

		if (self.enable_script_header):
			self.write_script(self.script_header)

		self.file.write('\nWorld ()\n')


	def write_tail(self):
		"""
		Write the final part of pyg file.
		"""
		self.file.write('\nRender ("%s")\n\n'
			% self.camera_name)

	def sequence(self, pass_name):
		self.pass_name = pass_name
		fileout = str(self.file_output_pass())

		dir,base = os.path.split(os.path.abspath(fileout))
		if not os.path.exists(dir):
			os.makedirs(dir)
		if not os.path.isdir(dir):
			raise GelatoError, 'Error: %s is not a directory' % dir

		try:
			self.file = open(fileout, 'w')
		except IOError:
			raise GelatoError, 'Cannot write file "%s"' % fileout

		try:
			self.write_head()

			self.active_lights = []
			self.all_lights = []
			self.default_geometrysets = ['camera',]

			if (self.enable_ray_traced):
				self.default_geometrysets.append('reflection')
				self.default_geometrysets.append('refraction')

			if (self.shadow_ray_traced):
				self.default_geometrysets.append('shadows')

			if (self.pass_name == 'Ambient Occlusion'):
				self.write_ambient_occlusion_pass1()

			if (self.pass_name == 'Beauty' or self.pass_name == 'Bake Diffuse'):
				if (self.enable_sky):
					self.write_background_color()

				if (self.enable_key_fill_rim):
					self.write_key_fill_rim()

				if (self.enable_lights):
					self.lights()

				if (self.shadow_ray_traced):
					self.write_shadow_ray_traced()

			if (self.pass_name == 'Beauty'):
				if (self.enable_ambient_occlusion):
					self.write_ambient_occlusion_pass2()

				if (self.enable_indirect_light):
					self.write_indirect_light()

			if (self.enable_debug_shaders and gelato_gui.debug_shader is not None):
				self.file.write('\n')
				self.write_shader(gelato_gui.debug_shader)

			self.geometries()

			self.write_tail()

		finally:
			self.file.close()


	def sequence_pass(self):
		self.fileobject_memo = []

		if (self.pass_shadows):
			self.sequence('Shadow')

		if (self.pass_ambient_occlusion):
			self.sequence('Ambient Occlusion')

		if (self.pass_bake_diffuse):
			self.sequence('Bake Diffuse')

		if (self.pass_beauty):
			self.sequence('Beauty')

	def write_command(self):
		if (self.pass_shadows):
			self.file.write('Command ("system", "string[2] argv", ("%s", "%s"))\n' %
				(GELATO, relative_filename(str(self.filename_shadows), str(self.filename))))

		if (self.pass_ambient_occlusion):
			self.file.write('Command ("system", "string[2] argv", ("%s", "%s"))\n' %
				(GELATO, relative_filename(str(self.filename_ambient_occlusion), str(self.filename))))
			self.file.write('Command ("system", "string[4] argv", ("%s", "-o", "%s", "%s"))\n' %
				(MAKETX, relative_filename(str(self.output_ambient_occlusion_tx), str(self.filename)), relative_filename(str(self.output_ambient_occlusion), str(self.filename))))

		if (self.pass_bake_diffuse):
			self.file.write('Command ("system", "string[2] argv", ("%s", "%s"))\n' %
				(GELATO, relative_filename(str(self.filename_bake_diffuse), str(self.filename))))

		if (self.pass_beauty):
			#self.file.write('Command ("system", "string[2] argv", ("%s", "%s"))\n' %
			#	(GELATO, relative_filename(str(self.filename_beauty), str(self.filename))))
			self.file.write('Input ("%s")\n' % relative_filename(str(self.filename_beauty), str(self.filename)))


	def setup(self):
		global permanents, gelato_gui

		for name, value in permanents.iteritems():
			if (name[0] == '_'):
				setattr(self, name[1:], value.val)
			else:
				setattr(self, name, value.val)

		# filename
		self.base = self.filename

		# compression

		comp = gelato_gui.menu_format.convert(self.format)[2]
		if (comp):
			self.compression = comp.convert(self.compression)
		else:
			self.compression = None

		self.compression_shadow = gelato_gui.menu_compression_tiff.convert(self.compression_shadow)

		# output file name image

		(self.format, self.suffix) = gelato_gui.menu_format.convert(self.format)[0:2]

		# output data

		(self.data_color, self.data_z) = gelato_gui.menu_data.convert(self.data)

		# bucketorder

		self.bucketorder = gelato_gui.menu_bucketorder.convert(self.bucketorder)

		# pixel filter

		self.filter = gelato_gui.menu_filter2.convert(self.filter)

		# units

		self.units_length = gelato_gui.menu_units.convert(self.units_length)


	def export(self, scene):
		global frappe

		# leave edit mode before getting the mesh

		if (Blender.Window.EditMode()):
			Blender.Window.EditMode(0)

		# verbosity

		self.verbose = 1
		try:
			rt = Blender.Get('rt')
			if (rt == 42):
				self.verbose = 1
			elif (rt == 43):
				self.verbose = 2
		except:
			pass

		self.scene = scene

		# setup variable from GUI

		self.setup()

		# passes

		self.npasses = 0
		if (self.pass_beauty):
			self.npasses += 1
		if (self.pass_shadows):
			self.npasses += 1
		if (self.pass_ambient_occlusion):
			self.npasses += 1
		if (self.pass_bake_diffuse):
			self.npasses += 1

		if (self.npasses == 0):
			raise GelatoError, 'No pass select'

		self.frame   = None
		self.nframes = None

		staframe = Blender.Get('staframe')
		curframe = Blender.Get('curframe')
		endframe = Blender.Get('endframe')

		self.mask = '.%%0%dd' % len('%d' % endframe)

		# file names, title and directoty

		self.filename = permanents['exportfile'].val
		baseDir = os.path.dirname(FILENAME_XML)
		filename = os.path.abspath(os.path.join(baseDir, self.filename))
		(self.base, self.ext) = os.path.splitext(filename)
		(self.directory, file) = os.path.split(filename)
		self.title = os.path.basename(self.base)

		self.filename                    = self.name_mask(self, '',                   self.ext)
		self.filename_beauty             = self.name_mask(self, '_beauty',            self.ext)
		self.filename_shadows            = self.name_mask(self, '_shadows',           self.ext)
		self.filename_ambient_occlusion  = self.name_mask(self, '_ambient_occlusion', self.ext)
		self.filename_bake_diffuse       = self.name_mask(self, '_bake_diffuse',      self.ext)

		if (self.suffix):
			self.output_color                = self.name_mask(self, '',                   self.suffix,      True)
			self.output_z                    = self.name_mask(self, '_z',                 self.suffix,      True)
			self.output_ambient_occlusion    = self.name_mask(self, '_ambient_occlusion', self.suffix,      True)
			self.output_ambient_occlusion_tx = self.name_mask(self, '_ambient_occlusion', self.TEXTURE_EXT, True)
			self.output_bake_diffuse         = self.name_mask(self, '_bake_diffuse',      self.suffix,      True)
			self.output_bake_diffuse_tx      = self.name_mask(self, '_bake_diffuse',      self.TEXTURE_EXT, True)

		if (self.verbose > 0):
			timestart = Blender.sys.time()
			print 'Info: starting Gelato pyg export to "%s"' % self.filename

		# set verbose

		for sd in frappe.global_effects.itervalues():
			if ( sd is not None ):
				sd.verbose = self.verbose
		for matShaders in frappe.mat_shader_groups.itervalues():
			for shaderGroup in matShaders:
				for sd in shaderGroup.shaders:
					if sd is not None:
						sd.verbose = self.verbose

		self.viewlayer = set(Blender.Window.ViewLayer())

		self.world   = Blender.World.GetCurrent()
		self.context = self.scene.getRenderingContext()
		self.sizex   = float(self.context.imageSizeX())
		self.sizey   = float(self.context.imageSizeY())

		if (not self.format):
			ret = Blender.Draw.PupMenu('No output format defined, continue anyhow?%t|no%x1|yes%x2')
			if (ret != 2):
				return

		try:
			if (self.enable_anim):

				#if (self.enable_viewer):
				#	ret = Blender.Draw.PupMenu('Window viewer enabled, continue ?%t|no%x1|yes%x2')
				#	if (ret != 2):
				#		return

				self.nframes = endframe - staframe + 1

				if (self.nframes <= 0):
					raise GelatoError, 'Invalid frame length'

				# all frames

				try:
					for self.frame in xrange(staframe, endframe + 1):
						Blender.Set('curframe', self.frame)

						if (self.verbose > 1):
							print 'Info: exporting frame %d' % self.frame

						self.sequence_pass()
				finally:
					Blender.Set('curframe', curframe)

			else:
				# single frame

				self.sequence_pass()

			# command file

			if ((self.npasses > 1) or self.pass_ambient_occlusion):

				self.frame = None

				try:
					self.file = open(str(self.filename), 'w')
				except IOError:
					raise GelatoError, 'Cannot write file "%s"' % self.filename

				try:
					if (self.enable_anim):
						for self.frame in xrange(staframe, endframe + 1):
							self.write_command()
					else:
						self.write_command()
				finally:
					self.file.close()

		finally:
			Blender.Window.DrawProgressBar(1.0, '')

		if (self.verbose > 0):
			print 'Info: finished Gelato pyg export (%.2fs)' % (Blender.sys.time() - timestart)


####################################################
# GUI handlers
####################################################
class cfggui(object):
	class panel(object):
		__slots__ = ['name', 'reg_name', 'func', 'help', 'id', 'button']

		def __init__(self, name, reg_name, func, help, enabled):
			self.name     = name
			self.reg_name = reg_name
			self.func     = func
			self.help     = help
			self.id       = 0
			self.button   = Blender.Draw.Create(enabled)

	class bake_menu(object):
		__slots__ = ['cookie', 'store', 'food']

		def __init__(self, title = None, options = None,):
			slist = []

			if (title):
				slist.append('%s %%t' % title)

			i = 0
			self.store = {}
			self.food  = {}
			for (name, data) in options:
				slist.append('|%s %%x%d' % (name, i))
				self.store[i] = data
				self.food[name] = i
				i += 1

			self.cookie = ''.join(slist)

		def convert(self, id):
			return self.store.get(id)

		def val(self, name):
			return self.food.get(name)

	def __init__(self):
		global WIN
		global frappe

		self.x0  = 10	# start cursor x
		self.y0  = 10	# start cursor y
		self.h   = 22	# height button
		self.s   = 30	# step y
		self.m   = 10	# margin button
		self.spd = 540	# space default button
		self.xBase = 10 # base x cursor position
		self.yBase = 10 # base y cursor position
		self.shader_size = 210 # size of shader button

		self.widgets = {}

		self.uniq_gui_id = 0
		self.gui_id_memo = {}
		self.menu_material = None
		self.menu_shaderGroup = None
		self.menu_layer    = None
		self.menu_text     = None

		self.shaderParamGUIIdMap = {}
		self.current_gui_shader = None

		self.user_transform_ids = {}

		self.panels = [
			self.panel('Output',         '_panel_output', self.panel_output, 'Panel output data', 1),
			self.panel('Geometries',     '_panel_geometries', self.panel_geometries, 'Panel geometries', 0),
			self.panel('Lights',         '_panel_lights', self.panel_lights, 'Panel lights', 0),
			self.panel('Shadows',        '_panel_shadows', self.panel_shadows, 'Panel select shadows type', 0),
			self.panel('Textures',       '_panel_textures', self.panel_textures, 'Panel textures', 0),
			self.panel('Environment',    '_panel_environment', self.panel_environment, 'Panel environment', 0),

			self.panel('Images',         '_panel_images', self.panel_images, 'Panel images', 0),
			self.panel('Shaders',        '_panel_shaders', self.panel_shaders, 'Panel shaders', 0),
			self.panel('Ray traced',     '_panel_ray_traced', self.panel_ray_traced, 'Panel ray traced', 0),
			self.panel('AO',             '_panel_ambient_occlusion', self.panel_ambient_occlusion, 'Panel ambient occlusion', 0),
			self.panel('Indirect Light', '_panel_indirectlight', self.panel_indirect_light, 'Panel indirect light', 0),
			self.panel('Depth of Field', '_panel_dof', self.panel_dof, 'Panel depth of field', 0),

			self.panel('Pass',           '_panel_pass', self.panel_pass, 'Panel select pass', 0),
			self.panel('SSS',            '_panel_sss', self.panel_sss, 'Panel subsurface scattering', 0),
			self.panel('Scripts',        '_panel_scripts', self.panel_scripts, 'Panel scripts', 0),
			self.panel('Transforms',     '_panel_transforms', self.panel_transforms, 'User transforms', 0),
		]

		self.shaderGroupInd = 0

		# ambient occlusion shader

		sd = None
		file_name = 'ambocclude.gso'

		try:
			fd = search_file(file_name, SHADERPATH)
			if (fd):
				sd = shader(file_name)
				self.ambient_occlusion_setup(sd)
		except:
			sys.excepthook(*sys.exc_info())
			print 'Error: shader "%s" not found. Ambient occlusion is disabled' % file_name

		frappe.global_effects['ambient_occlusion'] = sd

		# envlight shader

		sd = None
		file_name = 'envlight.gso'

		try:
			fd = search_file(file_name, SHADERPATH)
			if (fd):
				sd = shader(file_name, '__envlight_pass2__')
				self.occlusionmap_setup(sd)
		except:
			sys.excepthook(*sys.exc_info())
			print 'Error: shader "%s" not found. Environment light is disabled' % file_name

		frappe.global_effects['environment_light'] = sd

		# indirect light shader

		sd = None
		file_name = 'indirectlight.gso'

		try:
			fd = search_file(file_name, SHADERPATH)
			if (fd):
				sd = shader(file_name, '__indirectlight__')
		except:
			sys.excepthook(*sys.exc_info())
			print 'Error: shader "%s" not found. Indirect light is disabled' % file_name

		frappe.global_effects['indirect_light'] = sd

		# bake diffuse shader

		sd = None
		file_name = 'bakediffuse.gso'

		try:
			fd = search_file(file_name, SHADERPATH)
			if (fd):
				sd = shader(file_name)
		except:
			sys.excepthook(*sys.exc_info())
			print 'Error: shader "%s" not found. Bake diffuse is disabled' % file_name

		frappe.global_effects['bake_diffuse'] = sd

		# debug shaders

		self.debug_shader      = None
		self.menu_debug_shader = None

		list_sd = []
		for name in ['shownormals', 'showfacing', 'showst', 'showuv', 'showdudv', 'showgrids', 'raygoggles']:
			fd = search_file(name + '.gso', SHADERPATH)
			if (fd):
				try:
					sd = shader(name + '.gso')

					# only surface shaders

					if (sd.type is shader_types.surface):
						list_sd.append([name, sd])
				except:
					sys.excepthook(*sys.exc_info())
					print 'Error: shader "%s" not found.' % name

		if (len(list_sd) > 0):
			self.menu_debug_shader = self.bake_menu('Debug shaders', list_sd)

		# available shaders

		self.available_shader = find_files('*.gso', SHADERPATH)

		if (self.available_shader):
			shaderByType = {}
			for name in sorted(self.available_shader):
				if  (name.find('maya') == -1):
					fd = os.path.join(self.available_shader[name], name)
					try:
						sd = shader(os.path.basename(fd))

						# only surface shader and displacement shaders
						if ( sd.type is shader_types.surface or sd.type is shader_types.displacement ):
							list_surface = shaderByType.setdefault(sd.type, [])
							list_surface.append([name[:-4], sd])
						elif ( sd.type is shader_types.generic ):
							list_surface = shaderByType.setdefault(shader_types.surface, [])
							list_surface.append([name[:-4], sd])
							list_surface = shaderByType.setdefault(shader_types.displacement, [])
							list_surface.append([name[:-4], sd])
						
					except:
						sys.excepthook(*sys.exc_info())
						print 'Error: shader "%s" not found.' % name

			sTypeList = []
			sTypeList.append('Select Shader Type %t')
			i = 0
			for shaderType in shadergroup.available_types:
				sTypeList.append('|%s %%x%d' % ( str(shaderType), i))
				i = i + 1
			self.shaderTypeMenu = ''.join(sTypeList)
			self.shadermenu = {}
			self.shaderchoices = {}
			for shaderType, list_surface in shaderByType.iteritems():
				# build up the menu string
				slist = []

				slist.append('Select Shader %t')
				i = 0
				for (name, data) in list_surface:
					slist.append('|%s %%x%d' % (name, i))
					shaderchoices = self.shaderchoices.setdefault(shaderType, [])
					shaderchoices.append(data)
					i += 1

				self.shadermenu[shaderType] = ''.join(slist)

		# compression

		self.menu_compression_tiff = self.bake_menu('TIFF compression', [
			['None', 'none'],
			['ZIP',  'zip'],
			['LZW',  'lzw'],
		])

		self.menu_compression_openexr = self.bake_menu('OpenEXR compression', [
			['None',  'none'],
			['ZIP',   'zip'],
			['ZIPS',  'zips'],
			['PIZ',   'piz'],
			['PXR24', 'pxr24'],
		])

		# formats

		self.menu_format = self.bake_menu('Output format', [
			['Null',    [None,      None,    None, '']],
			['TIFF',    ['tiff',    '.tif',  self.menu_compression_tiff, 'TIFF compression']],
			['TARGA',   ['targa',   '.tga',  None, '']],
			['JPEG',    ['jpg',     '.jpg',  None, '']],
			['PNG',     ['png',     '.png',  None, '']],
			['PPM',     ['ppm',     '.ppm',  None, '']],
			['SGI',     ['DevIL',   '.sgi',  None, '']],
			['BMP',     ['DevIL',   '.bmp',  None, '']],
			['PCX',     ['DevIL',   '.pcx',  None, '']],
			['DDS',     ['DevIL',   '.dds',  None, '']],
			['RAW',     ['DevIL',   '.raw',  None, '']],
			['IFF',     ['iff',     '.iff',  None, '']],
			['OpenEXR', ['OpenEXR', '.exr',  self.menu_compression_openexr, 'OpenEXR compression']],
		])

		self.val_format_null    = self.menu_format.val('Null')
		self.val_format_openEXR = self.menu_format.val('OpenEXR')

		# data

		self.menu_data = self.bake_menu('Output data', [
			['RGB',      ['rgb',  None]],
			['RGBA',     ['rgba', None]],
			['Z',        ['z',    None]],
			['RGB + Z',  ['rgb',  'z']],
			['RGBA + Z', ['rgba', 'z']],
			['AvgZ',     ['avgz', None]],
			['VolZ',     ['volz', None]],
		])

		self.val_data_z = self.menu_data.val('Z')

		# bucket orders

		self.menu_bucketorder = self.bake_menu('Bucket order', [
			['Horizontal', 'horizontal'],
			['Vertical',   'vertical'],
			['Spiral',     'spiral'],
		])

		# filters

		filter = [
			['Gaussian',        'gaussian'],
			['Box',             'box'],
			['Triangle',        'triangle'],
			['Catmull-Rom',     'catmull-rom'],
			['Sinc',            'sinc'],
			['Blackman-Harris', 'blackman-harris'],
			['Mitchell',        'mitchell'],
			['B-Spline',        'b-spline'],
		]

		self.menu_filter1 = self.bake_menu('Pixel filter', filter)

		filter.extend([
			['Min',             'min'],
			['Max',             'max'],
			['Average',         'average'],
		])

		self.menu_filter2 = self.bake_menu('Pixel filter', filter)

		self.val_filter_min = self.menu_filter2.val('Min')

		# files extensions

		self.menu_files_extensions = self.bake_menu('files extensions', [
			['file.NNN.ext', 0],
			['file.ext.NNN', 1],
		])

		# units

		self.menu_units = self.bake_menu('Units', [
			['None',       None],
			['Millimeter', 'mm'],
			['Centimeter', 'cm'],
			['Meter',      'm'],
			['Kilometer',  'km'],
			['Inch',       'in'],
			['Foot',       'ft'],
			['Mile',       'mi'],
		])

		self.menu_file = self.bake_menu('File',
			[['Save', 'filemenu_save'],
			 ['Export', 'filemenu_export'],
			 ['Export As...', 'filemenu_export_as'],
			 ['Exit', 'filemenu_exit']])
		self.menu_file_ui = None

		self.home()
		self.reset_id()

	def get_gui_id(self, global_id):
		if (self.gui_id_memo.has_key(global_id)):
			return self.gui_id_memo[global_id]
		self.uniq_gui_id += 1
		if (self.uniq_gui_id > 16381):
			self.uniq_gui_id = 1
		self.gui_id_memo[global_id] = self.uniq_gui_id
		return self.uniq_gui_id

	def home(self):
		self.x = self.x0
		self.y = self.y0
		self.xBase = self.x0
		self.yBase = self.y0

	def inc_x(self, i = 0):
		self.x += i

	def inc_y(self, i = 0):
		self.y += i

	def line_feed(self, gap = True):
		self.x = self.xBase
		if (gap):
			self.inc_y(self.s)
		else:
			self.inc_y(self.h)

	def col_feed(self, i = 0, gap = True):
		self.y = self.yBase
		self.xBase = self.xBase + i
		if gap:
			self.xBase = self.xBase + self.m

	def update_yBase(self, i = 0):
		self.yBase = self.yBase + i

	def blank(self, x = 0):
		self.inc_x(x + self.m)

	def reset_id(self):
		self.id_buttons = dict()

	def get_id(self, global_id, func = None):
		id = self.get_gui_id(global_id)
		self.id_buttons[id] = func
		return id

	def draw_rect(self, x, y, w, h):
		Blender.BGL.glRecti(self.x + x, self.y + y, self.x + x + w, self.y + y + h)
		self.inc_x(w + self.m)

	def draw_text(self, s, size, x = 0, y = 0):
		Blender.BGL.glRasterPos2i(self.x + x, self.y + y)
		Blender.Draw.Text(s)
		self.inc_x(size + self.m)

	def draw_string(self, s, size, length, name, func = None, help = '', sep = None):
		rid = self.get_id(name, func)
		permanents[name] = Blender.Draw.String(s, rid, self.x, self.y, size, self.h,
			permanents[name].val, length, help)
		if (sep is None):
			self.inc_x(size + self.m)
		else:
			self.inc_x(size + sep)
		return rid

	def draw_number(self, s, size, min, max, name, func = None, help = '', sep = None):
		rid = self.get_id(name, func)
		permanents[name] = Blender.Draw.Number(s, rid, self.x, self.y, size, self.h,
			permanents[name].val, min, max, help)
		if (sep is None):
			self.inc_x(size + self.m)
		else:
			self.inc_x(size + sep)
		return rid

	def draw_slider(self, s, size, min, max, name, func = None, help = '', sep = None):
		rid = self.get_id(name, func)
		permanents[name] = Blender.Draw.Slider(s, rid, self.x, self.y, size, self.h,
			permanents[name].val, min, max, 0, help)
		if (sep is None):
			self.inc_x(size + self.m)
		else:
			self.inc_x(size + sep)
		return rid

	def draw_button(self, s, size, func, help = '', sep = None):
		rid = self.get_id(id(func), func)
		Blender.Draw.PushButton(s, rid, self.x, self.y, size, self.h, help)
		if (sep is None):
			self.inc_x(size + self.m)
		else:
			self.inc_x(size + sep)
		return rid

	def draw_toggle(self, s, size, name, func = None, help = '', sep = None):
		rid = self.get_id(name, func)
		permanents[name] = Blender.Draw.Toggle(s, rid, self.x, self.y, size, self.h,
			permanents[name].val, help)
		if (sep is None):
			self.inc_x(size + self.m)
		else:
			self.inc_x(size + sep)
		return rid

	def draw_menu(self, bake, size, name, func = None, help = '', sep = None):
		rid = self.get_id(name, func)
		permanents[name] = Blender.Draw.Menu(bake.cookie, rid, self.x, self.y, size, self.h,
			permanents[name].val, help)
		if (sep is None):
			self.inc_x(size + self.m)
		else:
			self.inc_x(size + sep)
		return rid

	def draw(self):
		self.home()
		self.reset_id()

		Blender.BGL.glClearColor(.5, .5, .5, 1.0)
		Blender.BGL.glClear(Blender.BGL.GL_COLOR_BUFFER_BIT)
		Blender.BGL.glColor3f(1.0, 1.0, 1.0)

		self.panel_common()
		self.line_feed()
		self.panel_select()

	@staticmethod
	def ambient_occlusion_setup(sd):
		sd['occlusionname'] = 'localocclusion'

	@staticmethod
	def occlusionmap_setup(sd):
		sd['occlusionmap'] = '$FILE_PASS1'


	def selectShaderGroupType(self):
		choice = -1
		shaderType = None
		if self.shaderTypeMenu is not None:
			choice = Blender.Draw.PupMenu(self.shaderTypeMenu, 10)
		if choice != -1:
			shaderType = shadergroup.available_types[choice]

		return shaderType


	def selectshader(self, shaderType):
		sd = None
		if (self.available_shader):
			choice = -1
			if self.shadermenu is not None:
				choice = Blender.Draw.PupMenu(self.shadermenu[shaderType], 10)
			if choice != -1:
				sd = self.shaderchoices[shaderType][choice]

		return sd


	def handle_event(self, evt, val):
		if ((evt == Blender.Draw.ESCKEY) or (evt == Blender.Draw.QKEY)):
			ret = Blender.Draw.PupMenu('OK?%t|Exit Frappe%x1')
			if (ret == 1):
				loader.xml_save()
				Blender.Draw.Exit()

	def handle_button_event(self, evt):
		global frappe
		global xmlDirty

		if (self.id_buttons.has_key(evt)):
			func = self.id_buttons[evt]
			if (func):
				func(evt)
		else:
			print 'no button registered for event %d' % evt

		if (permanents['format'].val == self.val_format_null):
			xmlDirty = xmlDirty or (permanents['enable_viewer'].val != 1)
			permanents['enable_viewer'].val = 1

		if (permanents['enable_dynamic'].val or permanents['shadow_ray_traced'].val):
			xmlDirty = xmlDirty or (permanents['pass_shadows'].val != 0)
			permanents['pass_shadows'].val = 0

		if (not permanents['enable_ambient_occlusion'].val):
			xmlDirty = xmlDirty or (permanents['pass_ambient_occlusion'].val != 0)
			permanents['pass_ambient_occlusion'].val = 0

		if (not permanents['enable_bake_diffuse'].val):
			xmlDirty = xmlDirty or (permanents['pass_bake_diffuse'].val != 0)
			permanents['pass_bake_diffuse'].val = 0

		if (not (permanents['pass_beauty'].val or permanents['pass_shadows'].val or
			permanents['pass_ambient_occlusion'].val or permanents['pass_bake_diffuse'].val)):
				xmlDirty = xmlDirty or (permanents['pass_beauty'].val != 1)
				permanents['pass_beauty'].val = 1

		if (permanents['data'].val < self.val_data_z):
			if (permanents['filter'].val >= self.val_filter_min):
				xmlDirty = xmlDirty or (permanents['filter'].val != 0)
				permanents['filter'].val = 0 # Gaussian

		#xml_save()
		Blender.Draw.Redraw(1)


	def cb_set_value(self, id, permanent):
		pass

	# fetch the currently-selected shader
	def get_current_shader(self):
		return self.current_gui_shader


	# shader functions
	def cb_shader_param_change(self, id):
		# look up the parameter in the currently-selected shader
		sd = self.get_current_shader()
		if sd is not None:
			name = self.shaderParamGUIIdMap.get(id)
			if name is not None:
				par = sd.parameters[name]
				ty  = par.type

				# float
				if (ty is literals.float):
					result = Blender.Draw.PupFloatInput(name + ': ', float(par.value), 0.0, 10000000.0, 1.0, 4.0)
					if result is not None:
						result = str(result)
				elif ty is literals.string:
					result = Blender.Draw.PupStrInput(name + ': ', par.value, 100)
				elif ty is literals.color:
					# TODO: color picker
					result = Blender.Draw.PupStrInput(name + ': ', par.value, 100)
				elif ty is literals.point:
					# TODO: PupBlock
					result = Blender.Draw.PupStrInput(name + ': ', par.value, 100)
				elif ty is literals.vector:
					# TODO: PupBlock
					result = Blender.Draw.PupStrInput(name + ': ', par.value, 100)
				elif ty is literals.normal:
					# TODO: PupBlock
					result = Blender.Draw.PupStrInput(name + ': ', par.value, 100)
				else:
					if (sd.verbose > 1):
						print 'Error: unknown parameter "%s"' % name
					result = None

				if result is not None:
					par.value = result
					par.change = True
			else:
				print 'ERROR: parameter not found for set parameter event callback'
		else:
			print 'ERROR: no current shader when setting parameter?'


	def shader_gui(self, sd):
		#print '%s: displaying GUI' % sd.layer
		self.current_gui_shader = sd
		Blender.BGL.glColor3f(0.0, 0.0, 0.0)
		Blender.BGL.glRasterPos2i(self.x+2, self.y+self.h/2-4)
		txt = 'Shader type "%s" name: ' % str(sd.type)
		Blender.Draw.Text(txt)
		Blender.BGL.glColor3f(1.0, 1.0, 0.8)
		Blender.BGL.glRasterPos2i(self.x+Blender.Draw.GetStringWidth(txt)+6, self.y+self.h/2-4)
		Blender.Draw.Text(sd.name)
		Blender.BGL.glColor3f(1.0, 1.0, 1.0)

		self.inc_y(self.s)
		i = 0

		self.shaderParamGUIIdMap = {}
		for name in sorted(sd.parameters, key=str.lower, reverse=True):
			par = sd.parameters[name]
			ty  = par.type

			# float
			buttonLabel = '%s: %s' % (name, par.value)
			#id_gui = self.draw_button(buttonLabel, sd.size, self.cb_shader_param_change, par.help)
			# almost, but not quite, draw_button.  we don't actually want the id defined by the parameter name
			# we want the id defined by the object that it's changing
			# TODO: might be worth refactoring draw_button to fix this
			id_gui = self.get_id(id(par), self.cb_shader_param_change)
			Blender.Draw.PushButton(buttonLabel, id_gui, self.x, self.y, self.shader_size, self.h, par.help)
			self.inc_x(self.shader_size + self.m)
			self.shaderParamGUIIdMap[id_gui] = name

			i += 1

			# color, point, vector, normal

			if (i % 3 == 0):
				self.line_feed(False)


	def cb_shader_toggle_enable_sss(self, id):
		widget = self.widgets[id]
		sd = self.get_current_shader()
		sd.enable_sss = int(widget.val)

	def cb_shader_set_sss_param(self, id):
		widget = self.widgets[id]
		sd = self.get_current_shader()
		sd.sss_param = widget.val

	def shader_gui_sss(self, sd):
		id = self.get_id('shader_enable_sss', self.cb_shader_toggle_enable_sss)
		self.widgets[id] = Blender.Draw.Toggle('Enable SSS', id, self.x, self.y, 100, self.h,
			sd.enable_sss, 'Enable SubSurface Scattering')
		self.inc_x(100 + self.m)

		#Blender.Draw.Toggle('Enable SSS', sd.id_enable_sss, self.x, self.y, 100, self.h,
		#	sd.enable_sss, 'Enable SubSurface Scattering')

		if (sd.enable_sss):
			id = self.get_id('shader_sss_param', self.cb_shader_set_sss_param)
			self.widgets[id] = Blender.Draw.String('parameter: ', id, self.x + 110, self.y, 210, self.h,
				sd.sss_param, 100, 'Name of parameter containing diffuse file from the first SSS pass')

	def cb_exit(self, id):
		self.handle_event(Blender.Draw.ESCKEY, 0)

	def cb_default(self, id):
		default_value()

	def cb_export(self, id):
		try:
			if xmlDirty:
				print 'xml is dirty - saving.  TODO: prompt'
				loader.xml_save()
			pyg.export(Blender.Scene.GetCurrent())
		except GelatoError, strerror:
			Blender.Draw.PupMenu('Error%t|"' + str(strerror) + '"')

	def cb_save(self, id):
		try:
			loader.xml_save()
		except GelatoError, strerror:
			Blender.Draw.PupMenu('Error%t|"' + str(strerror) + '"')

	def cb_render(self, id):
		global GELATO, permanents

		pyg.export(Blender.Scene.GetCurrent())
		xmlDir = os.path.dirname(FILENAME_XML)
		pygFile = os.path.abspath(os.path.join(xmlDir, permanents['exportfile'].val))
		pygDir = os.path.dirname(pygFile)
		pygFile = relative_filename(pygFile, pygFile)

		cmdargs = [GELATO,]
		if pyg.enable_viewer:
			# TODO: why doesn't specifying the data channel work correctly?
			#cmdargs.extend(['-iv', '-data', '"%s"' % pyg.data_color])
			cmdargs.extend(['-iv', '-data', '"all"'])
			#cmdargs.extend(['-iv',])
		cmdargs.extend(['-cwd', '"%s"' % pygDir, '"%s"' % pygFile])
		#if (WIN):
		#	cmd = '""%s" -cwd "%s" "%s""'
		#else:
		#	cmd = '"%s" -cwd "%s" "%s"'

		try:
			if (os.path.isfile(os.path.join(pygDir, pygFile))):
				#fullCmd = cmd % (GELATO, pygDir, pygFile)
				cmd = os.path.join(GELATOHOME, 'bin', GELATO)
				print 'Executing: %s with args %s' % (cmd, cmdargs)
				#os.system(fullCmd)
				os.spawnv(os.P_NOWAIT, cmd, cmdargs)
			else:
				Blender.Draw.PupMenu('Error%%t|"No such file %s"' % pygFile)
		except GelatoError, strerror:
			Blender.Draw.PupMenu('Error%t|"' + str(strerror) + '"')


	# callback when shader group selection is changed
	def cb_menu_shaderGroup(self, event):
		global xmlDirty
		global frappe
		# material selection updated
		material_name = self.menu_material.convert(permanents['_select_material'].val)
		group_name = self.menu_shaderGroup.convert(permanents['_select_shadergroup'].val)
		layer_name = self.menu_layer.convert(permanents['_select_layer'].val)

		# if the material doesn't exist in the mat_shader_groups dictionary yet,
		# create it and populate it with a default shader group
		matShaders = frappe.mat_shader_groups.setdefault(material_name, [shadergroup('Default', shader_types.surface)])

		shaderGroup = None
		for matShaderGroup in matShaders:
			if matShaderGroup.name == group_name:
				shaderGroup = matShaderGroup

		if group_name == 'New Shader Group':
			# create a new shader group
			groupType = self.selectShaderGroupType()
			if groupType is not None:
				group_name = Blender.Draw.PupStrInput('Group Name:', 'new group name', 60)
			if group_name != 'new group name':
				create = True
				for shaderGroup in matShaders:
					if shaderGroup.name == group_name:
						Blender.Draw.PupMenu('Group already exists%t|ok%x1')
						create = False
				if create:
					matShaders.append(shadergroup(group_name, groupType))
					permanents['_select_shadergroup'].val = len(matShaders)-1
					xmlDirty = True


	# callback when layer selection is changed
	def cb_menu_layer(self, event):
		global frappe
		# material selection updated
		material_name = self.menu_material.convert(permanents['_select_material'].val)
		group_name = self.menu_shaderGroup.convert(permanents['_select_shadergroup'].val)
		layer_name = self.menu_layer.convert(permanents['_select_layer'].val)

		# if the material doesn't exist in the mat_shader_groups dictionary yet,
		# create it and populate it with a default shader group
		matShaders = frappe.mat_shader_groups.setdefault(material_name, [shadergroup('Default', shader_types.surface)])

		shaderGroup = None
		for matShaderGroup in matShaders:
			if matShaderGroup.name == group_name:
				shaderGroup = matShaderGroup
				break

		if layer_name == 'New Layer':
			# create a new shader layer
			sd = self.selectshader(shaderGroup.type)
			if sd is not None:
				layer_name = Blender.Draw.PupStrInput('Layer Name:', 'new layer', 60)
				if layer_name != 'new layer':
					create = True
					for shader in shaderGroup.shaders:
						if shader.layer == layer_name:
							create = False
							Blender.Draw.PupMenu('Layer already exists%t|ok%x1')
							break
					if create:
						shaderData = copy.deepcopy(sd)
						shaderData.layer = layer_name
						if shaderData.type is shader_types.generic:
							shaderData.type = shaderGroup.type
						shaderGroup.shaders.append(shaderData)
						permanents['_select_layer'].val = len(shaderGroup.shaders) - 1
						#shaderData.update(event)
						xmlDirty = True
						Blender.Redraw()


	# callback when material selection is changed
	def cb_menu_material(self, event):
		global frappe
		# material selection updated
		material_name = self.menu_material.convert(permanents['_select_material'].val)
		group_name = self.menu_shaderGroup.convert(permanents['_select_shadergroup'].val)
		layer_name = self.menu_layer.convert(permanents['_select_layer'].val)

		# if the material doesn't exist in the mat_shader_groups dictionary yet,
		# create it and populate it with a default shader group
		matShaders = frappe.mat_shader_groups.setdefault(material_name, [shadergroup('Default', shader_types.surface)])

		shaderGroup = None
		for matShaderGroup in matShaders:
			if matShaderGroup.name == group_name:
				shaderGroup = matShaderGroup


	def cb_menu_text(self, id):
		global permanents

		permanents['script_header'] = Blender.Draw.Create(self.menu_text.convert(permanents['_select_script_header'].val))

	def cb_script_start_remove(self, id):
		global permanents

		permanents['script_header'] = Blender.Draw.Create('')

        # create a new shader layer
	def cb_insert_layer(self, id):
		global permanents
		global frappe

		material_name = self.menu_material.convert(permanents['_select_material'].val)
		group_name = self.menu_shaderGroup.convert(permanents['_select_shadergroup'].val)

		materialShaderGroups = frappe.mat_shader_groups.setdefault(material_name, [shadergroup('Default', shader_types.surface)])
		# JM TODO: currently-selected shader group
		shaderGroup = None
		for matShaderGroup in materialShaderGroups:
			if matShaderGroup.name == group_name:
				shaderGroup = matShaderGroup
		if shaderGroup is not None:
			sd = self.selectshader(shaderGroup.type)
			if sd is not None:
				orig_layer_name = self.menu_layer.convert(permanents['_select_layer'].val)
				layer_name = Blender.Draw.PupStrInput('Layer Name:', 'new layer', 60)
				if layer_name != 'new layer':
					create = True
					for shader in shaderGroup.shaders:
						if shader.layer == layer_name:
							create = False
							Blender.Draw.PupMenu('Layer already exists%t|ok%x1')
							break
					if create:
						shaderData = copy.deepcopy(sd)
						shaderData.layer = layer_name
						if shaderData.type is shader_types.generic:
							shaderData.type = shaderGroup.type

						newshaders = []
						idx = 0
						newSelIdx = 0
						for shader in shaderGroup.shaders:
							if shader.layer == orig_layer_name:
								# insert the new one
								newshaders.append(shaderData)
								newSelIdx = idx
							newshaders.append(shader)
							idx = idx + 1

						shaderGroup.shaders = newshaders
						permanents['_select_layer'].val = newSelIdx
						Blender.Redraw()

        # assign a new shader to a material
	# JM

        # remove a shader assigned to a material
	# JM
	def cb_remove(self, id):
		global permanents
		global frappe

		material_name = self.menu_material.convert(permanents['_select_material'].val)
		group_name = self.menu_shaderGroup.convert(permanents['_select_shadergroup'].val)
		layer_name = self.menu_layer.convert(permanents['_select_layer'].val)

		print 'Remove mat %s group %s layer %s' % (material_name, group_name, layer_name)
		materialShaderGroups = frappe.mat_shader_groups.setdefault(material_name, [shadergroup('Default', shader_types.surface)])
		# JM TODO: currently-selected shader group
		shaderGroup = None
		for matShaderGroup in materialShaderGroups:
			if matShaderGroup.name == group_name:
				shaderGroup = matShaderGroup
		sd = None
		if shaderGroup is not None:
			for shader in shaderGroup.shaders:
				if shader.layer == layer_name:
					sd = shader

		if sd is not None:
			ret = Blender.Draw.PupMenu('Remove layer %s ?%%t|no%%x1|yes%%x2' % layer_name)
			if (ret != 2):
				return

			try:
				shaderGroup.shaders.remove(sd)
				# remove any connections linked to the removed layer
				try:
					del shaderGroup.connections[layer_name]
				except KeyError:
					# ignore
					pass
				# remove any connections sourced from the removed layer
				for targetLayer, targetData in shaderGroup.connections.items():
					for targetParam, srcData in targetData.items():
						srcLayer, srcParam = srcData
						if srcLayer == layer_name:
							del targetData[targetParam]
					if len(targetData) == 0:
						del shaderGroup.connections[targetLayer]

				if len(shaderGroup.shaders) == 0:
					permanents['_select_layer'].val = -1
			except:
				#if (self.verbose > 0):
				sys.excepthook(*sys.exc_info())


	def menuselect(self, prompt, opts):
		result = None
		selectlist = []
		selectlist.append('%s %%t' % prompt)
		i = 0
		choices = []
		for name, val in opts:
			selectlist.append('|%s %%x%d' % (name, i))
			choices.append(val)
			i += 1

		menu = ''.join(selectlist)
		choice = Blender.Draw.PupMenu(menu, 10)
		if choice != -1:
			result = choices[choice]
		return result


	def cb_new_connection(self, id):
		global permanents
		global frappe

		material_name = self.menu_material.convert(permanents['_select_material'].val)
		group_name = self.menu_shaderGroup.convert(permanents['_select_shadergroup'].val)
		layer_name = self.menu_layer.convert(permanents['_select_layer'].val)

		materialShaderGroups = frappe.mat_shader_groups.setdefault(material_name, [shadergroup('Default', shader_types.surface)])
		# JM TODO: currently-selected shader group
		shaderGroup = None
		for matShaderGroup in materialShaderGroups:
			if matShaderGroup.name == group_name:
				shaderGroup = matShaderGroup
		sd = None
		if shaderGroup is not None:
			for shader in shaderGroup.shaders:
				if shader.layer == layer_name:
					sd = shader

		if sd is not None:
			# select the input parameter from the shader
			targetParamName = self.menuselect('Select Target Parameter', [(x, x) for x in sd.parameters.keys() + sd.globals.keys()])
			if targetParamName is not None:
				try:
					targetParam = sd.parameters[targetParamName]
				except KeyError:
					targetParam = sd.globals[targetParamName]
				sourceShader = self.menuselect('Select Source Layer', [(x.layer, x) for x in shaderGroup.shaders])
				if sourceShader is not None:
					sourceopts = []
					for param_name, param in sourceShader.outparams.iteritems():
						if param.type == targetParam.type or (targetParam.type == literals.color and param.type == literals.float):
							sourceopts.append((param_name, param_name))
							
					if len(sourceopts) == 0:
						Blender.Draw.PupMenu('No valid source parameters %t|OK%x1')

					sourceParamName = self.menuselect('Select Source Parameter', sourceopts)
					if sourceParamName is not None:
						targetconns = shaderGroup.connections.setdefault(layer_name, {})
						targetconns[targetParamName] = (sourceShader.layer, sourceParamName)



	# reset shader parameters to default
	# JM
	def cb_shader_default(self, id):
		global permanents
		global frappe

		material_name = self.menu_material.convert(permanents['_select_material'].val)
		group_name = self.menu_shaderGroup.convert(permanents['_select_shadergroup'].val)
		layer_name = self.menu_layer.convert(permanents['_select_layer'].val)
		try:
			shaderGroups = frappe.mat_shader_groups.get(material_name)
			# JM TODO: currently-selected shader group
			for shaderGroup in shaderGroups:
				if shaderGroup.name == group_name:
					shaderData = shaderGroup.shaders
					for shader in shaderData:
						if shader.layer == layer_name:
							shader.default()
		except:
			#if (self.verbose > 0):
			sys.excepthook(*sys.exc_info())

	def cb_debug_shader(self, id):
		sd = self.menu_debug_shader.convert(permanents['_select_debug_shader'].val)
		if (sd is not None):
			if ((self.debug_shader is not None) and (self.debug_shader is sd)):
				return
			self.debug_shader = sd

	def cb_ambient_occlusion_default(self, id):
		global frappe
		shader_ambient_occlusion = frappe.global_effects['ambient_occlusion']
		shader_ambient_occlusion.default()
		self.ambient_occlusion_setup(shader_ambient_occlusion)

	def cb_shader_envlight_default(self, id):
		global frappe
		shader_environment_light = frappe.global_effects['environment_light']
		shader_environment_light.default()
		self.occlusionmap_setup(shader_environment_light)

	def cb_shader_indirect_light_default(self, id):
		global frappe
		frappe.global_effects['indirect_light'].default()

	def cb_shader_bake_diffuse_default(self, id):
		global frappe
		frappe.global_effects['bake_diffuse'].default()

	def cb_catmull_clark(self, id):
		set_property_bool('gelato:catmull_clark')

	def cb_bake_diffuse(self, id):
		set_property_bool('gelato:bake_diffuse')

	def cb_shadows(self, id):
		if (id != self.id_shadow_maps):
			permanents['shadow_maps'].val = 0
		if (id != self.id_shadow_woo):
			permanents['shadow_woo'].val = 0
		if (id != self.id_shadow_raytraced):
			permanents['shadow_ray_traced'].val = 0

	def cb_panel(self, id):
		for pan in self.panels:
			if (pan.id != id):
				# Turn off all other panel buttons
				pan.button.val = 0

	#def cb_select(self, name):
	#	global permanents
	#	permanents['exportfile'].val = os.path.abspath(name)

	def cb_errorselect(self, name):
		global permanents

		permanents['errorfilename'].val = os.path.abspath(name)

	#def cb_filename(self, id):
	#	global permanents
	#
	#	Blender.Window.FileSelector(self.cb_select, '.pyg', permanents['exportfile'].val)

	def cb_errorfilename(self, id):
		global permanents

		Blender.Window.FileSelector(self.cb_errorselect, '.txt', permanents['errorfilename'].val)

	def _get_user_transform(self, title, transName):
		global frappe
		transData = frappe.user_transforms.get(transName)
		if transData is not None:
			hasTrans = True
		else:
			hasTrans = False
			transData = (0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0)

		#print 'name = %s / transData = %s' % (repr(transName), repr(transData))
		yawVal, pitchVal, rollVal, transXVal, transYVal, transZVal, scaleXVal, scaleYVal, scaleZVal = transData

		yaw = Blender.Draw.Create(yawVal)
		pitch = Blender.Draw.Create(pitchVal)
		roll = Blender.Draw.Create(rollVal)
		transX = Blender.Draw.Create(transXVal)
		transY = Blender.Draw.Create(transYVal)
		transZ = Blender.Draw.Create(transZVal)
		scaleX = Blender.Draw.Create(scaleXVal)
		scaleY = Blender.Draw.Create(scaleYVal)
		scaleZ = Blender.Draw.Create(scaleZVal)
		block = []
		block.append('')
		if hasTrans:
			block.append('Editing transform ' + transName)
		else:
			transElement = Blender.Draw.Create('')
			block.append(("Name: ", transElement, 0, 100, "Transform name"))
		block.append('')
		block.append('Rotation')
		block.append(("Yaw: ", yaw, 0.0, 360.0, "Rotation about Z axis"))
		block.append(("Pitch: ", pitch, 0.0, 360.0, "Rotation about X axis"))
		block.append(("Roll: ", roll, 0.0, 360.0, "Rotation about Y axis"))
		block.append('')
		block.append('Translation')
		block.append(("X: ", transX, 0.0, 10000.0, "X component of translation"))
		block.append(("Y: ", transY, 0.0, 10000.0, "Y component of translation"))
		block.append(("Z: ", transZ, 0.0, 10000.0, "Z component of translation"))
		block.append('Scale')
		block.append(("X: ", scaleX, 0.0, 10000.0, "X component of scale"))
		block.append(("Y: ", scaleY, 0.0, 10000.0, "Y component of scale"))
		block.append(("Z: ", scaleZ, 0.0, 10000.0, "Z component of scale"))

		#print 'title = %s' % repr(title)
		#print 'block = %s' % repr(block)
		result = Blender.Draw.PupBlock(title, block)

		if result:
			transData = (
				yaw.val, pitch.val, roll.val, 
				transX.val, transY.val, transZ.val,
				scaleX.val, scaleY.val, scaleZ.val )
			if not hasTrans:
				resultData = (transElement.val, transData)
			else:
				resultData = (transName, transData)
		else:
			resultData = (None, None)

		return resultData


	def cb_new_user_transform(self, id):
		global frappe
		transName, transData = self._get_user_transform('New Transform', '')

		if transName is not None:
			if len(transName) == 0:
				Blender.Draw.PupMenu('Error%t|Transform must have a name')
			elif not frappe.user_transforms.has_key(transName):
				frappe.user_transforms[transName] = transData
			else:
				Blender.Draw.PupMenu('Error%t|Tranform with name ' + transName + ' already exists!')
		

	def cb_edit_user_transform(self, id):
		global frappe
		origTransName = self.user_transform_ids.get(id)
		if origTransName is not None:
			transName, transData = self._get_user_transform('Edit transform ' + origTransName, origTransName)
			if transName is not None:
				if transName == origTransName:
					frappe.user_transforms[transName] = transData
				else:
					Blender.Draw.PupMenu('Error%t|Cannot rename transforms.  You must delete and re-create.')
		else:
			Blender.Draw.PupMenu('Error%t|Unable to find transform for event id')
			

	def cb_delete_user_transform(self, id):
		global frappe
		transName = self.user_transform_ids.get(id)
		if transName is not None:
			if frappe.user_transforms.has_key(transName):
				ret = Blender.Draw.PupMenu('Really delete transform ' + transName + '?%t|no%x1|yes%x2')
				if (ret != 2):
					return
				del frappe.user_transforms[transName]


	def filemenu_save(self):
		try:
			loader.xml_save()
		except GelatoError, strerror:
			Blender.Draw.PupMenu('Error%t|"' + str(strerror) + '"')

	def filemenu_export(self):
		try:
			if xmlDirty:
				print 'xml is dirty - saving.  TODO: prompt'
				loader.xml_save()
			pyg.export(Blender.Scene.GetCurrent())
		except GelatoError, strerror:
			Blender.Draw.PupMenu('Error%t|"' + str(strerror) + '"')

	def filemenu_export_as_selectfile_callback(self, name):
		# compute relative path
		newExport = relative_filename(name, FILENAME_XML)

		print 'new export file = %s' % newExport

		permanents['exportfile'].val = newExport
		self.filemenu_export()

	def filemenu_export_as(self):
		basePath = os.path.dirname(FILENAME_XML)
		defaultVal = os.path.abspath(os.path.join(basePath, permanents['exportfile'].val))
		Blender.Window.FileSelector(self.filemenu_export_as_selectfile_callback, 'Select file to export', defaultVal)

	def filemenu_exit(self):
		self.handle_event(Blender.Draw.ESCKEY, 0)

	def cb_menu_file(self, id):
		val = Blender.Draw.PupMenu(self.menu_file.cookie)

		try:
			func = self.menu_file.store[val]
			func = getattr(self, func)
			apply(func, ())
		except KeyError:
			print 'could not find value %d' % val
			pass

	def panel_common(self):
		self.draw_text('Frappe v' + __version__, 130, 2, 6)

		self.draw_button('File', 70,
			self.cb_menu_file, 'Application file menu')

		#self.draw_button('Save', 70,
		#	self.cb_save, 'Save pyg file')

		self.draw_button('Render', 80,
			self.cb_render, 'Save and render pyg file')

		self.blank(170)

		self.draw_button('Default', 80, self.cb_default,
			'Set all items to default values')

		#self.draw_button('Exit', 70, self.cb_exit,
		#	'Exit Python script')

	def panel_select(self):

		for pan in self.panels:
			if (pan.button.val):
				func = pan.func
				break
		else:
			self.panels[0].button.val = 1
			func = self.panels[0].func

		i = 0
		for pan in self.panels:
			#pan.id = self.draw_toggle(pan.name, 100, pan.reg_name, self.cb_panel, pan.help)
			pan.id = self.get_id(pan.reg_name, self.cb_panel)
			pan.button = Blender.Draw.Toggle(pan.name, pan.id, self.x, self.y, 100, self.h,
				pan.button.val, pan.help)
			self.inc_x(100 + self.m)

			self.line_feed(False)

		self.home()
		self.col_feed(102)
		self.line_feed()

		Blender.BGL.glColor3f(.2392, .3098, 1.0)
		self.draw_rect(2, 0, 2, self.h * len(self.panels))

		self.col_feed(0, False)
		self.line_feed()

		self.draw_rect(0, 0, 650, 2)
		Blender.BGL.glColor3f(1.0, 1.0, 1.0)

		self.col_feed(0)

		self.line_feed()
		self.update_yBase(18)
		self.update_yBase(self.s)
		self.inc_y(18)

		func()

	def panel_output(self):
		self.draw_toggle('Viewer', 100, 'enable_viewer',
			help = 'Enable window viewer')

		self.draw_toggle('Split', 100, 'enable_split',
			help = 'Split out objects into separate files')

		self.draw_toggle('Binary', 100, 'enable_binary',
			help = 'Enable binary file')

		self.draw_toggle('Anim', 100, 'enable_anim',
			help = 'Enable sequence render')

		if (permanents['enable_anim'].val):
			self.draw_menu(self.menu_files_extensions, 100, 'files_extensions',
				help = 'Templates files extensions')

		self.line_feed()

		Blender.BGL.glColor3f(0.0, 0.0, 0.0)
		self.draw_text('Bucket size', 100, 2, 6)
		Blender.BGL.glColor3f(1.0, 1.0, 1.0)

		l = 105
		v_min = 1
		v_max = 1024

		self.draw_number('X: ', l, v_min, v_max, 'bucketsize_x',
			help = 'Bucket size of pixel rectangles X', sep = 0)

		self.draw_number('Y: ', l, v_min, v_max, 'bucketsize_y',
			help = 'Bucket size of pixel rectangles Y')

		self.draw_menu(self.menu_bucketorder, 100, 'bucketorder',
			help = 'Render bucket order')

		self.line_feed()

		self.draw_toggle('Preview', 100, 'enable_preview',
			help = 'Enable preview')

		if (permanents['enable_preview'].val):
			self.draw_slider('Preview quality: ', 320, 0.0, 1.0, 'preview_quality',
				help = 'Preview quality')

		self.line_feed()

		self.draw_toggle('Enable error', 100, 'enable_error',
			help = 'Enable error file')

		if (permanents['enable_error'].val):
			self.draw_button('Error file:', 100, self.cb_errorfilename,
				'Select log file', 0)

			self.draw_string('', 440, 200, 'errorfilename',
				help = 'Error log file')

		self.line_feed()

		self.draw_text('Export file: %s' % permanents['exportfile'].val, 650, 2, 6)

		self.line_feed()

		self.draw_string('Input path: ', 650, 250, 'path_inputs',
			help = 'Search path for scene files')

		self.line_feed()

		self.draw_string('Texture path: ', 650, 250, 'path_texture',
			help = 'Search path for texture files')

		self.line_feed()

		self.draw_string('Shader path: ', 650, 250, 'path_shader',
			help = 'Search path for compiled shaders')

		self.line_feed()

		self.draw_string('ImageIO Path: ', 650, 250, 'path_imageio',
			help = 'Search path for image format input/output DSO\'s')

		self.line_feed()

		self.draw_string('Generator Path: ', 650, 250, 'path_generator',
			help = 'Search path for generators DSO\'s')

	def panel_images(self):
		self.draw_menu(self.menu_data, 105, 'data',
			help = 'Output data')

		self.draw_menu(self.menu_format, 105, 'format',
			help = 'Output format')

		(comp, comp_help) = self.menu_format.convert(permanents['format'].val)[2:4]
		if (comp):
			self.draw_menu(comp, 105, 'compression',
				help = comp_help)

		self.line_feed()

		Blender.BGL.glColor3f(0.0, 0.0, 0.0)
		self.draw_text('Spatial antialiasing', 105, 2, 6)
		Blender.BGL.glColor3f(1.0, 1.0, 1.0)

		l = 90
		v_min = 1
		v_max = 32

		self.draw_number('X: ', l, v_min, v_max, 'antialiasing_x',
			help = 'Spatial antialiasing X', sep = 0)

		self.draw_number('Y: ', l, v_min, v_max, 'antialiasing_y',
			help = 'Spatial antialiasing Y')

		self.draw_number('Gain: ', 105, 0.0, 16.0, 'gain',
			help = 'Image gain')

		self.draw_number('Gamma: ', 105, 0.0, 16.0, 'gamma',
			help = 'Image gamma')

		self.line_feed()

		Blender.BGL.glColor3f(0.0, 0.0, 0.0)
		self.draw_text('Pixel filter width', 105, 2, 6)
		Blender.BGL.glColor3f(1.0, 1.0, 1.0)

		l = 90
		v_min = 0.0
		v_max = 32.0

		self.draw_number('X: ', l, v_min, v_max, 'filterwidth_x',
			help = 'Pixel filter width X', sep = 0)

		self.draw_number('Y: ', l, v_min, v_max, 'filterwidth_y',
			help = 'Pixel filter width Y')

		if (permanents['data'].val < self.val_data_z):
			menu_filter = self.menu_filter1
		else:
			menu_filter = self.menu_filter2

		self.draw_menu(menu_filter, 130, 'filter',
			help = 'Pixel filter')

		self.line_feed()

		v = permanents['format'].val
		if ((v != self.val_format_null) and (v != self.val_format_openEXR)):

			Blender.BGL.glColor3f(0.0, 0.0, 0.0)
			self.draw_text('Quantize', 50, 2, 6)
			Blender.BGL.glColor3f(1.0, 1.0, 1.0)

			l = 120
			l_max = 20

			self.draw_string('zero: ', l, l_max, 'quantize_zero',
				help = 'Quantization parameter zero', sep = 0)

			self.draw_string('one: ', l, l_max, 'quantize_one',
				help = 'Quantization parameter one', sep = 0)

			self.draw_string('min: ', l, l_max, 'quantize_min',
				help = 'Quantization parameter min', sep = 0)

			self.draw_string('max: ', l, l_max, 'quantize_max',
				help = 'Quantization parameter max')

			self.draw_number('Dither: ', 100, 0.0, 10.0, 'dither',
				help = 'Dither amplitude')

	def panel_pass(self):
		self.draw_toggle('Beauty', 130, 'pass_beauty',
			help = 'Enable beauty pass')

		if ((not permanents['enable_dynamic'].val) and
			(permanents['shadow_maps'].val or permanents['shadow_woo'].val)):
				self.line_feed(False)
				self.draw_toggle('Shadows', 130, 'pass_shadows',
					help = 'Enable shadows pass')

		if (permanents['enable_ambient_occlusion'].val):
			self.line_feed(False)
			self.draw_toggle('Ambient Occlusion', 130, 'pass_ambient_occlusion',
				help = 'Enable ambient occlusion pass')

		if (permanents['enable_bake_diffuse'].val):
			self.line_feed(False)
			self.draw_toggle('Bake diffuse', 130, 'pass_bake_diffuse',
				help = 'Enable bake diffuse pass')

	def panel_geometries(self):
		self.draw_toggle('All double sided', 130, 'all_double_sided',
			help = 'Enable all double sided faces')

		self.draw_toggle('DupliVerts', 130, 'dup_verts',
			help = 'Enable DupliVerts')

		self.line_feed()

		self.draw_button('Catmull Clark', 130,
			self.cb_catmull_clark, 'Enable catmull-clark property of all selected objects')

		self.draw_button('Bake diffuse', 130,
			self.cb_bake_diffuse, 'Enable bake diffuse property of all selected objects')

		self.line_feed()

		# TODO: create new geometry set
		# TODO: list of existing geometry sets
		# TODO: remove existing geometry set

	def panel_lights(self):
		self.draw_toggle('Enable', 100, 'enable_lights',
			help = 'Enable all lights')

		self.draw_toggle('Key Fill Rim', 100, 'enable_key_fill_rim',
			help = 'Enable Key Fill Rim 3-lights')

		if (permanents['enable_lights'].val):
			self.draw_slider('Lights factor: ', 320, 0.0, 1000.0, 'lights_factor',
				help = 'Lights factor')

	def panel_shadows(self):
		self.id_shadow_maps = self.draw_toggle('Maps', 105, 'shadow_maps',
			self.cb_shadows, 'Enable shadow maps', sep = 0)

		self.id_shadow_woo = self.draw_toggle('Woo', 105, 'shadow_woo',
			self.cb_shadows, 'Enable Woo (average) shadow')

		self.id_shadow_raytraced = self.draw_toggle('Ray traced', 100, 'shadow_ray_traced',
			self.cb_shadows, 'Enable ray traced shadows')

		if (permanents['shadow_maps'].val or permanents['shadow_woo'].val):
			self.line_feed(False)

			self.draw_toggle('Dynamics', 210, 'enable_dynamic',
				help = 'Enable dynamic shadow')

			if (not permanents['enable_dynamic'].val):
				self.line_feed()

				self.draw_menu(self.menu_compression_tiff, 100, 'compression_shadow',
					help = 'Shadow compression')

	def panel_textures(self):
		self.draw_toggle('Enable', 100, 'enable_textures',
			help = 'Enable all textures')

		if (permanents['enable_textures'].val):
			self.draw_toggle('Automipmap', 100, 'enable_automipmap',
				help = 'Automatically generate mipmaps')

			self.line_feed()

			self.draw_string('Texture memory: ', 210, 30, 'limits_texturememory',
				help = 'Maximum texture cache size in kB')

			self.line_feed()

			self.draw_string('Texture files: ', 210, 30, 'limits_texturefiles',
				help = 'Maximum number of open texture file')

	# JM
	def panel_shaders(self):
		global frappe

		self.draw_toggle('Enable Shaders', 100, 'enable_shaders',
			help = 'Enable all shaders')

		if (permanents['enable_shaders'].val):

			self.draw_number('Shading quality: ', 160, 0.0, 16.0, 'shadingquality',
				help = 'Shading quality')

			self.draw_toggle('Debug Shaders', 100, '_enable_debug_shaders',
				help = 'Enable debug shaders')

			if (permanents['_enable_debug_shaders'].val):

				if (self.menu_debug_shader):
					self.draw_menu(self.menu_debug_shader, 100, '_select_debug_shader',
						self.cb_debug_shader, help = 'Select debug shader')

					if (self.debug_shader is not None):
						self.line_feed()

						self.y = self.shader_gui(debug_shader)
			else:
				# get all materials

				materials = Blender.Material.Get()
				if (materials):
					self.line_feed()

					self.menu_material = self.bake_menu('Materials',
						[[m.name, m.name] for m in sorted(materials)])

					self.draw_menu(self.menu_material, 100, '_select_material', help = 'Select material', func = self.cb_menu_material)

					# get the currently-selected material
					material_name = self.menu_material.convert(permanents['_select_material'].val)

					shaderGroups = frappe.mat_shader_groups.setdefault(material_name, [shadergroup('Default', shader_types.surface)])
					groupList = [[g.name, g.name] for g in shaderGroups]
					groupList.append(['New...', 'New Shader Group'])

					self.menu_shaderGroup = self.bake_menu('Shader Groups',
						groupList)

					# get the previous group name
					group_name = self.menu_shaderGroup.convert(permanents['_select_shadergroup'].val)

					self.draw_menu(self.menu_shaderGroup, 100, '_select_shadergroup', help = 'Select shader group', func=self.cb_menu_shaderGroup)


					# JM: currently-selected shader group
					shaderData = None
					selectedIdx = -1
					for idx, shaderGroup in enumerate(shaderGroups):
						if shaderGroup.name == group_name:
							shaderData = shaderGroup
							selectedIdx = idx
					if shaderData is None:
						group_name = shaderGroups[0]
						permanents['_select_shadergroup'].val = idx
						shaderData = shaderGroups[0]
				
					if self.menu_layer is not None:
						layer_name = self.menu_layer.convert(permanents['_select_layer'].val)
					else:
						layer_name = 'Select Layer'

					sd = None
					if shaderData is not None:
						if self.menu_layer:
							layer_name = self.menu_layer.convert(permanents['_select_layer'].val)
						else:
							layer_name = 'No Layer'
						layerList = []
						selectedIdx = -1
						for idx, shader in enumerate(shaderData.shaders):
							if shader.layer == layer_name:
								selectedIdx = idx
								shName = layer_name
								sd = shader
							layerStr = '%s : %s' % (idx+1, shader.layer)
							layerList.append([layerStr, shader.layer])

						if selectedIdx == -1 and len(shaderData.shaders) > 0:
							selectedIdx = 0
							sd = shaderData.shaders[0]
							shName = sd.layer

						layerList.append(['New...', 'New Layer'])

						self.menu_layer = self.bake_menu('Layers',
							layerList)

						self.draw_menu(self.menu_layer, 100, '_select_layer', help = 'Select layer', func=self.cb_menu_layer)

						if len(shaderData.shaders) > 0:
							self.draw_button('Insert Layer', 100, self.cb_insert_layer, 'Insert a new Shader Layer')
							self.draw_button('Remove Layer', 100, self.cb_remove, 'Remove the current Shader Layer')


					self.line_feed()
					baseY = self.y

					if sd is not None:
						# display connections
						connectData = shaderData.connections.get(sd.layer)
						if connectData is not None:
							# will be a dict of { target parameter : ( source layer, source parameter )}
							for paramName, sourceData in connectData.iteritems():
								sourceLayer, sourceParam = sourceData
								self.draw_text(paramName, 120, 0, 0)
								self.draw_text('connected from', 120, 0, 0)
								self.draw_text(sourceLayer, 120, 0, 0)
								self.draw_text(sourceParam, 120, 0, 0)
								self.line_feed()
						self.draw_button('New Connection', 100, self.cb_new_connection, 'Create a new connection')

						# if the shader has a bake_diffuse
						if (permanents['enable_bake_diffuse'].val):
							self.line_feed()
							self.shader_gui_sss(sd)

						self.line_feed()

						# draw the shader's GUI
						self.shader_gui(sd)

						self.blank(self.spd)



	def panel_dof(self):
		self.draw_toggle('Enable', 100, 'enable_dof', help = 'Enable Depth Of Field')

		if (permanents['enable_dof'].val):
			self.draw_string('F/Stop: ', 100, 20, 'fstop',
				help = 'F/Stop for depth of field')

			self.draw_string('Focal length: ', 160, 20, 'focallength',
				help = 'Lens focal length')

			self.draw_number('Quality: ', 100, 1, 128, 'dofquality',
				help = 'Number of lens values for DoF')

	def panel_environment(self):
		self.draw_toggle('Sky', 60, 'enable_sky',
			help = 'Enable background color')

		self.draw_menu(self.menu_units, 100, 'units_length',
			help = 'Physical length units of "common" space')

		if (permanents['units_length'].val):
			self.draw_string('Length scale: ', 140, 20, 'units_lengthscale',
				help = 'Length unit scale of "common" space units')


	def panel_ambient_occlusion(self):
		global frappe

		self.draw_toggle('Enable', 100, 'enable_ambient_occlusion',
			help = 'Enable ambient occlusion')

		if (permanents['enable_ambient_occlusion'].val):

			shader_ambient_occlusion = frappe.global_effects['ambient_occlusion']
			if (shader_ambient_occlusion):
				self.line_feed()

				y = self.shader_gui(shader_ambient_occlusion)

				self.blank(self.spd)

				self.draw_button('Default', 100, self.cb_ambient_occlusion_default,
					'Ambient occlusion default values')

				self.y = y

			shader_environment_light = frappe.global_effects['environment_light']
			if (shader_environment_light):
				self.line_feed()

				y = self.shader_gui(shader_environment_light)

				self.blank(self.spd)

				self.draw_button('Default', 100,
					self.cb_shader_envlight_default, 'Environment light default values')

				self.y = y

	def panel_indirect_light(self):
		global frappe
		self.draw_toggle('Enable', 100, 'enable_indirect_light',
			help = 'Enable indirect light')

		if (permanents['enable_indirect_light'].val):
			shader_indirect_light = frappe.global_effects['indirect_light']
			if (shader_indirect_light is not None):

				self.draw_number('Min samples: ', 140, 0, 16, 'indirect_minsamples',
					help = 'The minimum number of nearby samples')

				self.line_feed()

				y = self.shader_gui(shader_indirect_light)

				self.blank(self.spd)

				self.draw_button('Default', 100,
					self.cb_shader_indirect_light_default, 'Indirect light default values')

				self.y = y

	def panel_ray_traced(self):
		self.draw_toggle('Enable', 100, 'enable_ray_traced',
			help = 'Enable ray traced reflections and refractions')

		if (permanents['enable_ray_traced'].val):

			self.draw_toggle('Opaque shadows', 120, 'ray_traced_opaque_shadows',
				help = 'Enable objects opaque regardless of their shaders')

			if (permanents['shadow_ray_traced'].val):
				self.draw_number('Shadow bias: ', 140, 0, 16, 'ray_traced_shadow_bias',
					help = 'Ray traced shadow bias')

			self.draw_number('Raytraced max depth: ', 170, 0, 16, 'ray_traced_max_depth',
				help = 'Ray traced max depth')

	def panel_sss(self):
		global frappe
		self.draw_toggle('Enable', 100, 'enable_bake_diffuse',
			help = 'Enable bake diffuse')

		if (permanents['enable_bake_diffuse'].val):
			shader_bake_diffuse = frappe.global_effects['bake_diffuse']
			if (shader_bake_diffuse is not None):
				self.line_feed()

				y = self.shader_gui(shader_bake_diffuse)

				self.blank(self.spd)

				self.draw_button('Default', 100, self.cb_shader_bake_diffuse_default,
					'Bake diffuse default values')

				self.y = y

	def panel_scripts(self):
		self.draw_toggle('Script header', 100, 'enable_script_header',
			help = 'Enable script header')

		if (permanents['enable_script_header'].val):
			l = 100
			script = permanents['script_header'].val
			if (script):
				self.draw_button('Remove', 100,
					self.cb_script_start_remove, 'Remove script header')

				self.draw_text(script, l, 2, 6)
			else:
				texts = Blender.Text.Get()
				if (texts):
					self.menu_text = self.bake_menu('Load script from text',
						[[t.name, t.name] for t in texts])

					self.draw_menu(self.menu_text, l, '_select_script_header',
						self.cb_menu_text, 'Select script header')

	def panel_transforms(self):
		global frappe

		self.draw_button('New', 70,
			self.cb_new_user_transform, 'Create a new user-defined transform')
		self.line_feed()

		for transName, transData in frappe.user_transforms.iteritems():
			self.draw_text(transName, 100, 2, 6)
			rid = self.get_id(id(frappe.user_transforms[transName]), self.cb_delete_user_transform)
			print 'Delete: %s -> %s' % ( rid, transName )
			self.user_transform_ids[rid] = transName
			Blender.Draw.PushButton('Delete', rid, self.x, self.y, 100, self.h, 'Delete transform')
			self.inc_x(100 + self.m)
			rid = self.get_id(id(frappe.user_transforms[transName]), self.cb_edit_user_transform)
			print 'Edit: %s -> %s' % ( rid, transName )
			self.user_transform_ids[rid] = transName
			Blender.Draw.PushButton('Edit', rid, self.x, self.y, 100, self.h, 'Edit transform')
			self.inc_x(100 + self.m)
			self.line_feed()


# property

def set_property_bool(name):
	for obj in Blender.Object.GetSelected():
		ty = obj.type
		if ((ty != 'Mesh') and (ty != 'Surf')):
			continue
		try:
			try:
				prop = obj.getProperty(name)
				obj.removeProperty(prop)
			except:
				pass

			obj.addProperty(name, 1, 'BOOL')
			Blender.Redraw()
			Blender.Window.RedrawAll()
		except:
			sys.excepthook(*sys.exc_info())

def get_property_bool(obj, name):
	try:
		prop = obj.getProperty(name)
		if (prop.type == 'BOOL'):
			return prop.getData()
	except:
		pass

	return False


# utility to fetch all frappe properties in a dictionary
def get_frappe_properties(obj):
	frappeProps = {}
	propList = obj.getAllProperties()
	for prop in propList:
		if prop.name[:7] == 'frappe:':
			frappeProps[prop.name[7:]] = prop
	return frappeProps


# XML data

def default_value():
	global permanents
	global FILENAME_PYG, WIN
	global frappe

	# additional paths beyond the defaults
	path_shader    = ''
	path_texture   = ''
	path_inputs    = ''
	path_imageio   = ''
	path_generator = ''

	if (WIN):
		texturefiles = '100'
	else:
		texturefiles = '1000'

	permanents = {
		'exportfile':                 Blender.Draw.Create(FILENAME_PYG),

		'enable_anim':                Blender.Draw.Create(0),
		'files_extensions':           Blender.Draw.Create(0),		# file.NNN.ext

		'enable_binary':              Blender.Draw.Create(1),

		'enable_split':               Blender.Draw.Create(0),

		'enable_script_header':       Blender.Draw.Create(0),
		'script_header':              Blender.Draw.Create(''),
		'_select_script_header':      Blender.Draw.Create(0),

		'bucketorder':                Blender.Draw.Create(2),		# Spiral
		'bucketsize_x':               Blender.Draw.Create(32),
		'bucketsize_y':               Blender.Draw.Create(32),

		'enable_error':               Blender.Draw.Create(0),
		'errorfilename':              Blender.Draw.Create('>>gelato_log.txt'),

		'enable_preview':             Blender.Draw.Create(0),
		'preview_quality':            Blender.Draw.Create(0.1),

		'enable_viewer':              Blender.Draw.Create(1),
		'format':                     Blender.Draw.Create(0),		# Null
		'data':                       Blender.Draw.Create(0),		# RGB

		'compression':                Blender.Draw.Create(1),		# ZIP
		'compression_shadow':         Blender.Draw.Create(1),		# ZIP

		'shadow_maps':                Blender.Draw.Create(0),
		'shadow_woo':                 Blender.Draw.Create(0),
		'shadow_ray_traced':          Blender.Draw.Create(0),
		'enable_dynamic':             Blender.Draw.Create(0),

		'antialiasing_x':             Blender.Draw.Create(4),
		'antialiasing_y':             Blender.Draw.Create(4),

		'filter':                     Blender.Draw.Create(0),		# Gaussian
		'filterwidth_x':              Blender.Draw.Create(2.0),
		'filterwidth_y':              Blender.Draw.Create(2.0),

		'gamma':                      Blender.Draw.Create(1.0),
		'gain':                       Blender.Draw.Create(1.0),

		'dither':                     Blender.Draw.Create(0.5),

		'quantize_zero':              Blender.Draw.Create('0'),
		'quantize_one':               Blender.Draw.Create('255'),
		'quantize_min':               Blender.Draw.Create('0'),
		'quantize_max':               Blender.Draw.Create('255'),

		'all_double_sided':           Blender.Draw.Create(0),
		'dup_verts':                  Blender.Draw.Create(1),

		'enable_ray_traced':          Blender.Draw.Create(0),
		'ray_traced_max_depth':       Blender.Draw.Create(1),
		'ray_traced_shadow_bias':     Blender.Draw.Create(0.01),
		'ray_traced_opaque_shadows':  Blender.Draw.Create(1),

		'lights_factor':              Blender.Draw.Create(50.0),
		'enable_key_fill_rim':        Blender.Draw.Create(0),
		'enable_lights':              Blender.Draw.Create(1),

		'enable_shaders':             Blender.Draw.Create(1),
		'shadingquality':             Blender.Draw.Create(1.0),
		'_enable_debug_shaders':      Blender.Draw.Create(0),
		'_select_debug_shader':       Blender.Draw.Create(0),
		'_select_material':           Blender.Draw.Create(0),
		'_select_shadergroup':        Blender.Draw.Create(0),
		'_select_layer':              Blender.Draw.Create(0),
		'_select_shader':             Blender.Draw.Create(0),

		'enable_ambient_occlusion':   Blender.Draw.Create(0),

		'enable_bake_diffuse':        Blender.Draw.Create(0),

		'enable_indirect_light':      Blender.Draw.Create(0),
		'indirect_minsamples':        Blender.Draw.Create(3),

		'enable_textures':            Blender.Draw.Create(1),
		'enable_automipmap':          Blender.Draw.Create(1),

		'enable_dof':                 Blender.Draw.Create(0),
		'fstop':                      Blender.Draw.Create('4.0'),
		'focallength':                Blender.Draw.Create('0.032'),
		'dofquality':                 Blender.Draw.Create(16),

		'enable_sky':                 Blender.Draw.Create(1),
		'units_length':               Blender.Draw.Create(0),
		'units_lengthscale':          Blender.Draw.Create('1.0'),

		'limits_texturememory':       Blender.Draw.Create('20480'),
		'limits_texturefiles':        Blender.Draw.Create(texturefiles),

		'path_shader':                Blender.Draw.Create(path_shader),
		'path_texture':               Blender.Draw.Create(path_texture),
		'path_inputs':                Blender.Draw.Create(path_inputs),
		'path_imageio':               Blender.Draw.Create(path_imageio),
		'path_generator':             Blender.Draw.Create(path_generator),

		'pass_beauty':                Blender.Draw.Create(1),
		'pass_shadows':               Blender.Draw.Create(0),
		'pass_ambient_occlusion':     Blender.Draw.Create(0),
		'pass_bake_diffuse':          Blender.Draw.Create(0),
	}

	for mat in frappe.mat_shader_groups.itervalues():
		for shaderGroup in mat:
			for sd in shaderGroup.shaders:
				if sd is not None:
					sd.default()

def relative_filename(filename, relativeto):
	"""Computes the relative path to a file."""
	topath, tofile = os.path.split(fix_file_name(filename))
	frompath, fromfile = os.path.split(fix_file_name(relativeto))
	topath = fix_file_name(os.path.normpath(topath))
	frompath = fix_file_name(os.path.normpath(frompath))
	commonpart = os.path.commonprefix([topath, frompath])
	lastpart = os.path.basename(commonpart)
	if ( len(commonpart) > 0 and commonpart[-1] != '/' ):
		reallycommon, extra = os.path.split(commonpart)
		if extra != lastpart:
			commonpart = reallycommon
		# otherwise the "extra" was really just the final path segment
	relpath = frompath.replace(commonpart, '')
	extrapath = topath.replace(commonpart, '')
	updirs = ''
	while relpath != '' and relpath != '/':
		updirs = os.path.join(updirs, '..')
		relpath, relfile = os.path.split(relpath)
	result = os.path.join(updirs, extrapath, tofile)
	result = fix_file_name(result)
	return result


def output_filename_xml():
	return  fix_file_name(FILENAME_XML)


####################################################
# XML save and load
####################################################
class FrappeXML(object):
	def __init__(self):
		# Table of load version to function name used to load that version
		self._loadVersionFuncs = { 
			'0.15' : self.xml_load015,
			'0.151' : self.xml_load0151
		}
		self.verbose = 1
	
	def GetTextValue(self, rootElement, tagName):
		elem = rootElement.getElementsByTagName(tagName)
		if (len(elem) == 0):
			print 'invalid element %s' % tagName
			return None
		elem[0].normalize()
		nd = elem[0].firstChild
		if (nd.nodeType != xml.dom.Node.TEXT_NODE):
			print 'invalid node type on %s' % tagName
			return None
		return nd.data.strip()

	def save_shader(self, sd, document, root):
		if (not sd.file):
			return False

		# file

		el = document.createElement('file')
		root.appendChild(el)
		el.appendChild(document.createTextNode(sd.file))

		# nameid

		if (sd.nameid):
			el = document.createElement('nameid')
			root.appendChild(el)
			el.appendChild(document.createTextNode(sd.nameid))

		if sd.layer is not None:
			el = document.createElement('layer')
			root.appendChild(el)
			el.appendChild(document.createTextNode(sd.layer))

		# shader's parameter
		for name, par in sd.parameters.iteritems():
			if (not par.change):
				continue

			el = document.createElement('parameter')
			root.appendChild(el)
			el.setAttribute('name', name)

			el.appendChild(document.createTextNode(par.value))

		return True

	def load_shader(self, root):
		# file

		el = root.getElementsByTagName('file')
		if (len(el) == 0):
                        print 'unable to load shader from XML, no "file" element'
			return None

		el[0].normalize()
		filename = el[0].firstChild.data.strip()

		# nameid

		nid = ''
		el = root.getElementsByTagName('nameid')
		if (len(el) > 0):
			el[0].normalize()
			nid = el[0].firstChild.data.strip()
			#print 'shader nameid = %s' % nid

		# re-init object

		try:
			sd = shader(filename, nid)
			#sd.__init__(filename, nid)
		except:
                        print 'Failed to initialize shader from file %s, name id %s' % (filename, nid)
			return None

		el = root.getElementsByTagName('layer')
		if len(el) > 0:
			el[0].normalize()
			sd.layer = el[0].firstChild.data.strip()

		# shader's parameter

		for attr in root.getElementsByTagName('parameter'):
			name = attr.getAttribute('name')
			if (sd.parameters.has_key(name)):
				attr.normalize()
				sd.parameters[name].value = attr.firstChild.data.strip()
				sd.parameters[name].change     = True

		return sd


	def xml_save(self):
		global ROOT_ELEMENT, USE_XML_DOM_EXT
		global permanents
		global frappe
	
		# write xml file
	
		dom = xml.dom.minidom.getDOMImplementation()
		doctype = dom.createDocumentType(ROOT_ELEMENT, None, None)
	
		doc = dom.createDocument(None, ROOT_ELEMENT, doctype )
	
		root = doc.documentElement
		doc.appendChild(root)
	
		root.setAttribute('version', __version__)
		root.setAttribute('saveformat', __currentSaveVersion__)
		root.setAttribute('timestamp', datetime.datetime.today().strftime('%Y-%m-%d %H:%M:%S'))
		if USE_GETPASS:
			root.setAttribute('user', getpass.getuser())
		root.setAttribute('platform', sys.platform)
	
		head = doc.createElement('config')
		root.appendChild(head)
	
		for name in sorted(permanents.iterkeys()):
			# skip internal's names
			if (name[0] == '_'):
				continue
	
			elem = doc.createElement(name)
			head.appendChild(elem)
			elem.appendChild(doc.createTextNode(str(permanents[name].val).strip()))
	
		# materials list
	
		blender_materials = [m.name for m in Blender.Material.Get()]
	
		# global effects first
		globfx = doc.createElement('global_effects')
		root.appendChild(globfx)
		for mat, sd in sorted(frappe.global_effects.iteritems()):
			if ( sd is None ):
				continue
			material = doc.createElement('shader')
			globfx.appendChild(material)
			material.setAttribute('name', mat)
			self.save_shader(sd, doc, material)
	
		# assigned materials
		materials = doc.createElement('materials')
		root.appendChild(materials)
		for matname, mat in sorted(frappe.mat_shader_groups.iteritems()):
			if ( matname not in blender_materials ):
				continue
			if mat is None:
				continue
			material = doc.createElement('material')
			materials.appendChild(material)
			material.setAttribute('name', matname)
	
			for shaderGroupInfo in mat:
				shaderGroup = doc.createElement('shaderGroup')
				material.appendChild(shaderGroup)
				shaderGroup.setAttribute('name', shaderGroupInfo.name)
				shaderGroup.setAttribute('type', str(shaderGroupInfo.type))
				for shaderIdx, sd in enumerate(shaderGroupInfo.shaders): 
					if sd is not None:
						shader = doc.createElement('shader')
						shader.setAttribute('enable_sss', str(sd.enable_sss))
						shader.setAttribute('sss_parameter', sd.sss_param)
						shader.setAttribute('index', str(shaderIdx))
	
						self.save_shader(sd, doc, shader)
	
						shaderGroup.appendChild(shader)

				# save connections
				for targetLayer, connections in shaderGroupInfo.connections.iteritems():
					for targetParam, sourceData in connections.iteritems():
						sourceLayer, sourceParam = sourceData
						conn = doc.createElement('connection')
						targetLayerElem = doc.createElement('targetLayer')
						targetLayerElem.appendChild(doc.createTextNode(targetLayer))
						conn.appendChild(targetLayerElem)
						targetParamElem = doc.createElement('targetParam')
						targetParamElem.appendChild(doc.createTextNode(targetParam))
						conn.appendChild(targetParamElem)
						sourceLayerElem = doc.createElement('sourceLayer')
						sourceLayerElem.appendChild(doc.createTextNode(sourceLayer))
						conn.appendChild(sourceLayerElem)
						sourceParamElem = doc.createElement('sourceParam')
						sourceParamElem.appendChild(doc.createTextNode(sourceParam))
						conn.appendChild(sourceParamElem)
						shaderGroup.appendChild(conn)

		user_trans = doc.createElement('transforms')
		root.appendChild(user_trans)
		for transname, transdata in sorted(frappe.user_transforms.iteritems()):
			trans = doc.createElement('transform')
			user_trans.appendChild(trans)
			trans.setAttribute('name', transname)
			yaw, pitch, roll, transX, transY, transZ, scaleX, scaleY, scaleZ = transdata
			elem = doc.createElement('yaw')
			elem.appendChild(doc.createTextNode(str(yaw)))
			trans.appendChild(elem)
			elem = doc.createElement('pitch')
			elem.appendChild(doc.createTextNode(str(pitch)))
			trans.appendChild(elem)
			elem = doc.createElement('roll')
			elem.appendChild(doc.createTextNode(str(roll)))
			trans.appendChild(elem)
			elem = doc.createElement('transX')
			elem.appendChild(doc.createTextNode(str(transX)))
			trans.appendChild(elem)
			elem = doc.createElement('transY')
			elem.appendChild(doc.createTextNode(str(transY)))
			trans.appendChild(elem)
			elem = doc.createElement('transZ')
			elem.appendChild(doc.createTextNode(str(transZ)))
			trans.appendChild(elem)
			elem = doc.createElement('scaleX')
			elem.appendChild(doc.createTextNode(str(scaleX)))
			trans.appendChild(elem)
			elem = doc.createElement('scaleY')
			elem.appendChild(doc.createTextNode(str(scaleY)))
			trans.appendChild(elem)
			elem = doc.createElement('scaleZ')
			elem.appendChild(doc.createTextNode(str(scaleZ)))
			trans.appendChild(elem)

		# write XML file
	
		filename_xml = output_filename_xml()
	
		try:
			fxml = open(filename_xml, 'w')
	
		except IOError:
	
			print 'Error: Cannot write file "%s"' % filename_xml
			return
	
		print 'Saving to %s...' % filename_xml
		if (USE_XML_DOM_EXT):
			xml.dom.ext.PrettyPrint(doc, fxml)
		else:
			doc.writexml(fxml, addindent = '  ', newl = '\n')
	
	def xml_load0151(self, doc):
		"""
		Load version 0.151 save files
		"""
		global permanents
		global frappe
		head = doc.getElementsByTagName('config')
		if (len(head) == 0):
			print 'Error: file "%s", not element "config"' % filename_xml
		else:
			for name in permanents.keys():
				# skip internal's names
				if (name[0] == '_'):
					continue
	
				el = head[0].getElementsByTagName(name)
				if (len(el) == 0):
					continue
	
				el[0].normalize()
				nd = el[0].firstChild
				if (nd.nodeType != xml.dom.Node.TEXT_NODE):
					continue
	
				try:
					ty = type(permanents[name].val)
					if (ty is int):
						permanents[name] = Blender.Draw.Create(int(nd.data))
					elif (ty is float):
						permanents[name] = Blender.Draw.Create(float(nd.data))
					elif (ty is str):
						permanents[name] = Blender.Draw.Create(nd.data.strip())
					else:
						print 'Error: file "%s", element "%s" type "%s" unknown' % (filename_xml, name, ty)
				except:
					if (self.verbose > 0):
						sys.excepthook(*sys.exc_info())
	
		# materials list
	
		blender_materials = [m.name for m in Blender.Material.Get()]
	
		for globfx in doc.getElementsByTagName('global_effects'):
			for shaderElem in globfx.getElementsByTagName('shader'):
				name = shaderElem.getAttribute('name')
				if (name is None):
					continue
	
				sd = self.load_shader(shaderElem)
	
				if sd is None:
					continue
	
				frappe.global_effects[name] = sd
	
		for material in doc.getElementsByTagName('materials'):
			for mat in material.getElementsByTagName('material'):
	
				matName = mat.getAttribute('name')
				if (matName is None):
					continue
	
				if (matName not in blender_materials):
					continue
	
				frappe.mat_shader_groups[matName] = [shadergroup('Default', shader_types.surface)]
	
				for shaderGroupElement in mat.getElementsByTagName('shaderGroup'):
					shaderGroupName = shaderGroupElement.getAttribute('name')
					shaderGroupType = shaderGroupElement.getAttribute('type')
					if shaderGroupType is None:
						shaderGroupType = shader_types.surface
					else:
						shaderGroupType = shader_types[shaderGroupType]
	
					if shaderGroupName != 'Default':
						shaderGroup = shadergroup(shaderGroupName, shaderGroupType)
						frappe.mat_shader_groups[matName].append(shaderGroup)
					else:
						shaderGroup = frappe.mat_shader_groups[matName][0]
					shaderGroupValues = shaderGroup.shaders
					shaderGroupShaders = []
					for shaderElement in shaderGroupElement.getElementsByTagName('shader'):
						shaderIdx = shaderElement.getAttribute('index')
						shaderIdx = int(shaderIdx)
	
						sd = self.load_shader(shaderElement)
	
						if sd is None:
                                                        print 'unable to load shader from xml'
							continue
	
						try:
							enable_sss = shaderElement.getAttribute('enable_sss')
							if (enable_sss):
								sd.enable_sss = int(enable_sss)
						except:
							if (self.verbose > 0):
								sys.excepthook(*sys.exc_info())
	
						try:
							sss_parameter = shaderElement.getAttribute('sss_parameter')
							if (sss_parameter):
								sd.sss_param = sss_parameter.strip()
						except:
							if (self.verbose > 0):
								sys.excepthook(*sys.exc_info())
						if sd.type is shader_types.generic:
							sd.type = shaderGroupType
	
						shaderGroupShaders.append((shaderIdx, sd))
					shaderGroupShaders.sort()
					for idx, sd in shaderGroupShaders:
						shaderGroupValues.append(sd)

					# load connections
					for conn in shaderGroupElement.getElementsByTagName('connection'):
						targetLayerName = self.GetTextValue(conn, 'targetLayer')
						targetParamName = self.GetTextValue(conn, 'targetParam')
						sourceLayerName = self.GetTextValue(conn, 'sourceLayer')
						sourceParamName = self.GetTextValue(conn, 'sourceParam')
						#targetLayer = conn.getElementsByTagName('targetLayer')
						#if (len(targetLayer) == 0):
						#	print 'invalid target layer'
						#	continue
						#targetLayer[0].normalize()
						#nd = targetLayer[0].firstChild
						#if (nd.nodeType != xml.dom.Node.TEXT_NODE):
						#	print 'invalid target node type'
						#	continue
						#targetLayerName = nd.data.strip()
	
						#targetParam = conn.getElementsByTagName('targetParam')
						#if (len(targetParam) == 0):
						#	print 'invalid target param'
						#	continue
						#targetParam[0].normalize()
						#nd = targetParam[0].firstChild
						#if (nd.nodeType != xml.dom.Node.TEXT_NODE):
						#	print 'invalid target param node type'
						#	continue
						#targetParamName = nd.data.strip()
	
						#sourceLayer = conn.getElementsByTagName('sourceLayer')
						#if (len(sourceLayer) == 0):
						#	print 'invalid source layer'
						#	continue
						#sourceLayer[0].normalize()
						#nd = sourceLayer[0].firstChild
						#if (nd.nodeType != xml.dom.Node.TEXT_NODE):
						#	print 'invalid source layer node type'
						#	continue
						#sourceLayerName = nd.data.strip()
	
						#sourceParam = conn.getElementsByTagName('sourceParam')
						#if (len(sourceParam) == 0):
						#	print 'invalid source param'
						#	continue
						#sourceParam[0].normalize()
						#nd = sourceParam[0].firstChild
						#if (nd.nodeType != xml.dom.Node.TEXT_NODE):
						#	print 'invalid source param node type'
						#	continue
						#sourceParamName = nd.data.strip()

						layerConnects = shaderGroup.connections.setdefault(targetLayerName, {})
						layerConnects[targetParamName] = (sourceLayerName, sourceParamName)

		for transParent in doc.getElementsByTagName('transforms'):
			for transElem in transParent.getElementsByTagName('transform'):
				transName = transElem.getAttribute('name')
				if (transName is None):
					continue
				transName = str(transName)
				yaw = float(self.GetTextValue(transElem, 'yaw'))
				pitch = float(self.GetTextValue(transElem, 'pitch'))
				roll = float(self.GetTextValue(transElem, 'roll'))
				transX = float(self.GetTextValue(transElem, 'transX'))
				transY = float(self.GetTextValue(transElem, 'transY'))
				transZ = float(self.GetTextValue(transElem, 'transZ'))
				scaleX = float(self.GetTextValue(transElem, 'scaleX'))
				scaleY = float(self.GetTextValue(transElem, 'scaleY'))
				scaleZ = float(self.GetTextValue(transElem, 'scaleZ'))

				frappe.user_transforms[transName] = (yaw, pitch, roll, transX, transY, transZ, scaleX, scaleY, scaleZ)
	
	
	def xml_load015(self, doc):
		"""
		Load version 0.15 save files
		"""
		global permanents
		global frappe
		head = doc.getElementsByTagName('config')
		if (len(head) == 0):
			print 'Error: file "%s", not element "config"' % filename_xml
		else:
			for name in permanents.keys():
				# skip internal's names
				if (name[0] == '_'):
					continue
	
				el = head[0].getElementsByTagName(name)
				if (len(el) == 0):
					continue
	
				el[0].normalize()
				nd = el[0].firstChild
				if (nd.nodeType != xml.dom.Node.TEXT_NODE):
					continue
	
				try:
					ty = type(permanents[name].val)
					if (ty is int):
						permanents[name] = Blender.Draw.Create(int(nd.data))
					elif (ty is float):
						permanents[name] = Blender.Draw.Create(float(nd.data))
					elif (ty is str):
						permanents[name] = Blender.Draw.Create(nd.data.strip())
					else:
						print 'Error: file "%s", element "%s" type "%s" unknown' % (filename_xml, name, ty)
				except:
					if (self.verbose > 0):
						sys.excepthook(*sys.exc_info())
	
		# materials list
	
		blender_materials = [m.name for m in Blender.Material.Get()]
	
		for globfx in doc.getElementsByTagName('global_effects'):
			for mat in globfx.getElementsByTagName('material'):
				name = mat.getAttribute('name')
				if (name is None):
					continue
	
				sd = self.load_shader(mat)
	
				if sd is None:
					continue
	
				try:
					enable_sss = mat.getAttribute('enable_sss')
					if (enable_sss):
						sd.enable_sss = int(enable_sss)
				except:
					if (self.verbose > 0):
						sys.excepthook(*sys.exc_info())
	
				try:
					sss_parameter = mat.getAttribute('sss_parameter')
					if (sss_parameter):
						sd.sss_param = sss_parameter.strip()
				except:
					if (self.verbose > 0):
						sys.excepthook(*sys.exc_info())
	
				frappe.global_effects[name] = sd
	
		for material in doc.getElementsByTagName('materials'):
			for mat in material.getElementsByTagName('material'):
	
				matName = mat.getAttribute('name')
				if (matName is None):
					continue
	
				if (matName not in blender_materials):
					continue
	
				frappe.mat_shader_groups[matName] = [shadergroup('Default', shader_types.surface)]
	
				for shaderGroup in mat.getElementsByTagName('shaderGroup'):
					shaderGroupName = shaderGroup.getAttribute('name')
	
					shaders = []
					shaderInds = []
					for shad in shaderGroup.getElementsByTagName('shader'):
						shaderLayer = shad.getAttribute('name')
						shaderIdx = shad.getAttribute('index')
						shaderIdx = int(shaderIdx)
						if shaderIdx == 0:
							shaderGroupName = 'Default'
	
						sd = self.load_shader(shad)
	
						if sd is None:
							continue
	
						try:
							enable_sss = shad.getAttribute('enable_sss')
							if (enable_sss):
								sd.enable_sss = int(enable_sss)
						except:
							if (self.verbose > 0):
								sys.excepthook(*sys.exc_info())
	
						try:
							sss_parameter = shad.getAttribute('sss_parameter')
							if (sss_parameter):
								sd.sss_param = sss_parameter.strip()
						except:
							if (self.verbose > 0):
								sys.excepthook(*sys.exc_info())
	
						sd.layer = shaderLayer
	
						shaderInds.append((shaderIdx, sd))
	
					shaderInds.sort()
					for idx, sd in shaderInds:
						shaders.append(sd)
					if len(shaders) > 0:
						if shaderGroupName == 'Default':
							frappe.mat_shader_groups[matName][0].shaders = shaders
						else:
							frappe.mat_shader_groups[matName].append(shadergroup(shaderGroupName, shader_types.surface))
							frappe.mat_shader_groups[matName][-1].shaders = shaders
	
	
	def load_legacy(self, doc):
		"""
		Load legacy files prior to versioning
		"""
		global permanents
		global frappe
		head = doc.getElementsByTagName('config')
		if (len(head) == 0):
			print 'Error: file "%s", not element "config"' % filename_xml
		else:
			for name in permanents.keys():
				# skip internal's names
				if (name[0] == '_'):
					continue
	
				el = head[0].getElementsByTagName(name)
				if (len(el) == 0):
					continue
	
				el[0].normalize()
				nd = el[0].firstChild
				if (nd.nodeType != xml.dom.Node.TEXT_NODE):
					continue
	
				try:
					ty = type(permanents[name].val)
					if (ty is int):
						permanents[name] = Blender.Draw.Create(int(nd.data))
					elif (ty is float):
						permanents[name] = Blender.Draw.Create(float(nd.data))
					elif (ty is str):
						permanents[name] = Blender.Draw.Create(nd.data.strip())
					else:
						print 'Error: file "%s", element "%s" type "%s" unknown' % (filename_xml, name, ty)
				except:
					if (self.verbose > 0):
						sys.excepthook(*sys.exc_info())
	
		# materials list
	
		blender_materials = [m.name for m in Blender.Material.Get()]
	
		for material in doc.getElementsByTagName('materials'):
			index = material.getAttribute('index')
			if (index is None):
				print 'Error: file "%s", not attribute "index" element "materials"' % filename_xml
				continue
	
			idx = int(index)
	
			for mat in material.getElementsByTagName('material'):
	
				name = mat.getAttribute('name')
				if (name is None):
					continue
	
				if ((idx > 0) and (name not in blender_materials)):
					continue
	
				frappe.mat_shader_groups[matName] = [shadergroup('Default', shader_types.surface)]
				shaderDataSet = frappe.mat_shader_groups[matName][0].shaders
	
				sd = self.load_shader(mat)
	
				if sd is None:
					continue
	
				try:
					enable_sss = mat.getAttribute('enable_sss')
					if (enable_sss):
						sd.enable_sss = int(enable_sss)
				except:
					if (self.verbose > 0):
						sys.excepthook(*sys.exc_info())
	
				try:
					sss_parameter = mat.getAttribute('sss_parameter')
					if (sss_parameter):
						sd.sss_param = sss_parameter.strip()
				except:
					if (self.verbose > 0):
						sys.excepthook(*sys.exc_info())
	
				#materials_assign[idx][name] = sd
	
				shaderDataSet[0] = sd
	
	
	def xml_load(self):
		global ROOT_ELEMENT
		global xmlDirty
	
		# read xml file
	
		filename_xml = output_filename_xml()
		print 'Loading data from %s' % filename_xml
	
		try:
			doc = xml.dom.minidom.parse(os.path.normpath(filename_xml))
		except:
			import traceback
			traceback.print_exc()
			print 'Info: XML config file "%s" not found, will use default settings' % filename_xml
			xmlDirty = True
			return
	
		if (doc.documentElement.tagName != ROOT_ELEMENT) and (doc.documentElement.tagName != 'BlenderGelato'):
			print 'Error: file "%s", invalid root element "%s"' % (filename_xml, doc.documentElement.tagName)
			return
	
		root = doc.documentElement
		loadVersion = root.getAttribute('saveformat')
		loadFunc =  self._loadVersionFuncs.get(loadVersion)
		if loadFunc is not None:
			apply(loadFunc, (doc,))
		else:
			print 'Using legacy loading for version "%s"' % (loadVersion,)
			load_legacy(doc)
	
		# convert to relative path
		if os.path.isabs(permanents['exportfile'].val):
			newExport = relative_filename(os.path.abspath(permanents['exportfile'].val), filename_xml)
			#exportpath, exportfile = os.path.split(permanents['exportfile'].val)
			#xmlpath, xmlfile = os.path.split(filename_xml)
			#commonRoot = os.path.commonprefix([exportpath, xmlpath])
			#newExport = exportfile.replace(commonRoot, '')
			permanents['exportfile'].val = newExport
	
		xmlDirty = False
	
# utility

def escape_quote(name):
	return name.replace('"', '\\"')

def space2underscore(name):
	return re.sub('\s+', '_', name)

def fix_file_name(name):
	if (os.path.sep == '\\'):
		# replace '\' to '/'
		return name.replace('\\', '/')
	return name

def fix_vars(name):
	global WIN

	if (WIN):
		# replace $var to %var%
		return re.sub('\$(\w+)', '%\\1%', name)
	return name

def search_file(name, paths):
	for p in paths.split(':'):
		try:
			path = os.path.expandvars(p)
			file = os.path.join(path, name)
			if (os.path.exists(file)):
				return file
		except:
			continue
	return None

def find_files(pattern, paths):
	fdict = {}
	for p in paths.split(':'):
		try:
			path = os.path.expandvars(p)
			files = os.listdir(path)
			for f in fnmatch.filter(files, pattern):
				fdict[f] = path
		except:
			continue
	return fdict

# main

def main():
	global ROOT_ELEMENT, FILENAME_PYG
	global FILENAME_XML
	global GELATO, MAKETX, WIN
	global GELATOHOME
	global permanents
	global gelato_gui
	global pyg
	global loader
	global frappe
	global SHADERPATH, TEXTUREPATH, INPUTPATH, IMAGEIOPATH, GENERATORPATH

	PYTHON_MAJOR = 2
	PYTHON_MINOR = 4

	if (sys.version_info < (PYTHON_MAJOR, PYTHON_MINOR)):
		raise ('Error: Python version %d.%d or greater is required\nPython version is %s' % (PYTHON_MAJOR, PYTHON_MINOR, sys.version))

	ROOT_ELEMENT = 'Frappe'

	# programs

	GELATO  = 'gelato'
	MAKETX  = 'maketx'

	if (sys.platform[:3] == 'win'):
		WIN = True
		#exe = '.exe'
		#GELATO  += exe
		#MAKETX  += exe
	else:
		WIN = False

	GELATOHOME = os.getenv('GELATOHOME')
	if (GELATOHOME):
		print 'Info: GELATOHOME = "%s"' % GELATOHOME

		#GELATO  = os.path.join(GELATOHOME, 'bin', GELATO)
		#MAKETX  = os.path.join(GELATOHOME, 'bin', MAKETX)

		SHADERPATH    = '.:' + os.path.join(GELATOHOME, 'shaders')
		TEXTUREPATH   = '.:' + os.path.join(GELATOHOME, 'textures')
		INPUTPATH     = '.:' + os.path.join(GELATOHOME, 'inputs')
		IMAGEIOPATH   = '.:' + os.path.join(GELATOHOME, 'lib')
		GENERATORPATH = '.:' + os.path.join(GELATOHOME, 'lib')

	else:
		raise GelatoError('GELATOHOME environment variable not set.')
		return -1

	# file name

	try:
		blend_file_name = Blender.Get('filename')
		(base, ext) = os.path.splitext(blend_file_name)
		if (ext.lower() == '.gz'):
			(base, ext) = os.path.splitext(base)
	except:
		base = 'gelato'

	FILENAME_PYG = base + '.pyg'
	FILENAME_XML = base + '.xml'

	# create the data object
	frappe = Frappe()

	gelato_gui = None

	# set up default values
	default_value()

	# gelato convert
	pyg = gelato_pyg(FILENAME_PYG)

	# GUI
	gelato_gui = cfggui()

	# load and save xml file
	loader = FrappeXML()
	loader.xml_load()
	#xml_save()

	# start
	Blender.Draw.Register(gelato_gui.draw, gelato_gui.handle_event, gelato_gui.handle_button_event)

if __name__ == '__main__':
	try:
		import psyco
		psyco.full()
#		psyco.log()
#		psyco.profile()
	except:
		pass

	main()

